Search code examples
bashscpwildcard-expansion

Pass wildcard to scp in a wrapper bash script


I am writing a bash wrapper for scp'ing into and from a certain host with a certain username, like:

johny@bonjour:~/bin$ cat scpphcl 
#!/bin/bash

download=false
upload=false
local=""
remote=""

usage()
{
    echo "Usage: $0 -d[-u] -l <LocalPath> -r <RemotePath>"
    exit 1
}

while getopts "h?dul:r:" opt; do
    case "$opt" in
    h|\?)
        usage
        ;;
    d)
        download=true
        upload=false
        ;;
    u)
        download=false
        upload=true
        ;;
    l)
        local=$OPTARG
        ;;
    r)
        remote=$OPTARG
        ;;
    esac
done

if [[ -z $local || -z $remote ]]; then
    echo "Need to provide local and remote path."
    usage
fi

if $download; then
    scp somebody@somehost:"$remote" $local
elif $upload; then
    scp $local somebody@somehost:"$remote"
else
    echo "Neither download nor upload?"
    exit 1
fi

if [[ $? -ne 0 ]]; then
    echo "Something wrong happened in the scp process."
    exit 1
fi

exit 0

It works well with the usual filenames, but if there is any wildcard in the local filename field, it will not work right.

johny@bonjour:~/test$ scpphcl -u -l * -r /u/somebody/temp
Need to provide local and remote path.
Usage: /Users/johny/bin/scpphcl -d[-u] -l <LocalPath> -r <RemotePath>

There is a walkaround, using sinqle quotes around the local file argument if there is a wildcard in it:

johny@bonjour:~/test$ scpphcl -u -l '*' -r /u/somebody/temp

But even this walkaround will not work, if the command is issued outside the folder test:

johny@bonjour:~/test$ cd ..
johny@bonjour:~$ scpphcl -u -l 'test/*' -r /u/somebody/temp

This doesn't work and will hang in the scp process.

Any help in how to pass the wildcard in local filenames with the bash wrapper?


Solution

  • It's probably best not to require your users to quote wildcard patterns. I'd instead change the interface of your program to accept any number of local paths, after the option arguments:

    echo "Usage: $0 [-d|-u] [-r <RemotePath>] <LocalPath>..."
    

    When reading options, consume them with shift:

    while getopts "h?dur:" opt; do
        case "$opt" in
        h|\?)
            usage
            exit 0
            ;;
        d)
            download=true
            upload=false
            ;;
        u)
            download=false
            upload=true
            ;;
        r)
            remote="$OPTARG"
            ;;
        *)
            usage >&2
            exit 1
            ;;
        esac
    done
    shift $((OPTIND-1))
    

    Now the remaining positional arguments are the local filenames (and can be accessed with "$@" - note the all-important double-quotes there):

    if test -z "$*"  # no LocalPath arguments!
    then usage >&2; exit 1
    elif $download
    then exec scp somebody@somehost:"$remote" "$@"
    elif $upload
    then exec scp "$@" somebody@somehost:"$remote"
    fi