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?
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/