bashscp

Function calling scp behaves unexpectedly when using * wildcard


I have a shell function to help me scp files to and from servers. Both servers are running bash on Ubuntu 22.04. Let's call the server comp2 and pretend its IP address for is 192.168.1.2

On my server in the /home/.bashrc file I have this as my alias:

tocomp2() {
scp "$1" comp2@192.168.1.2:"$2"
}
fromcomp2() {
scp comp2@192.168.1.2:"$1" "$2"
}

Let's pretend I have three files foo1 foo2 and foo3 in my working directory, and three files bar bar2 bar3 in the home directory of comp2. Odd things happen when I use the wildcard to move these files.

tocomp2 foo* /home/ only transfers foo1

tocomp2 foo\* /home/ doesn't find any files.

fromcomp2 bar* ./ or fromcomp2 bar\* ./ transfers all three bar files

EDIT: Better question to ask is, how can I make tocomp2 act like fromcomp2 where the wildcard actually selects all foo files?


Solution

  • foo* expands to as many arguments as there are matching files (or the one literal argument foo* if no matches are found). If you have the files foo1 foo2 and foo3 in your directory then it expands to three arguments so tocomp2 foo* /home/ has four arguments in total and you are only referencing the first two in your code.

    foo\* passes the literal argument foo*, and the quotes in "$1" disable the pathname expansion so it remains a literal argument.

    A common fix is to put the known arguments in front and the variable arguments at the end of the call, then you can use "$@" to reference them all without knowing the exact count.

    tocomp2() {
      dest="$1" ; shift # capture the known argument and then discard from the arg list
      scp "$@" comp2@192.168.1.2:"$dest"
    }
    tocomp2 /home/ foo*
    

    If you don't like the reversed syntax then you can pull the last argument off the list and build a new array with everything except it:

    tocomp2() {
      dest="${@: -1}" # destination is the last argument
      files=( "${@:1:$(( $# - 1 ))}" )
      scp "${files[@]}" comp2@192.168.1.2:"$dest"
    }
    tocomp2 foo* /home/