简体   繁体   中英

bash script to perform operation on each argument against the final argument

Suppose you want to make a bash script which supports no options but acts like cp , because the cp supplied by your system does not accept multiple sources.

The usage for the system's (hypothetical and broken) cp is:

cp source target    # target may be a directory

The usage for the script will be:

cp.sh source... target    # target must be a directory

Here's a starting point for the script:

#!/bin/bash
tgt="$1"
shift
for src in "$@"; do
    echo cp $src $tgt
done

When run with the arguments " abcd " (note that d is the target), it outputs:

cp b a
cp c a
cp d a

The goal is to fix the script to output this instead, while keeping the code simple:

cp a d
cp b d
cp c d

/test.bash source1 source2 target1

#!/bin/bash

target=${!#} 

if [ ! -d $target ] ; then
    echo "$target must be a directory " >&2
    exit 1;
fi

args=("$@")
unset args[${#args[@]}-1]

for src in "${args[@]}"; do
    echo cp $src $target
done

will output

cp source1 target1
cp source2 target1

You can use array slicing to leave off the last of the arguments:

tgt="${!#}"
for src in "${@:1:$#-1}"; do
    cp "$src" "$tgt"
done

Why? The cp command already does that. Do a man cp and you will see.

If you still insist, here are two ways to get the last argument. Method 1: place command line in an array and extract the last element:

arg=("$@")
last_arg=${arg[(($# - 1))]}

The first line puts the command line arguments into the array arg. If your command line contains abcd then arg[0] == 'a', ... argv[3] == 'd'.

The second line extract the last argument. The (($# - 1)) takes the number of arguments (4 in this case), subtract 1 from it (to get 3). That expression then becomes:

last_arg=${arg[3]}

which points to the last argument.


The second method is not very portable, it makes use of the BASH_ARGV variable, which is $@ but in reverse order. If your command line is abcd then ${BASH_ARGV[0]} == 'd', ... ${BASH_ARGV[3]} == 'a':

last_arg=${BASH_ARGV[0]}

I hope this helps.

#!/bin/bash
tgt="${@: -1}"    # get the last parameter
for src in "$@"; do
    if [[ $src != $tgt ]]; then
        echo cp "$src" "$tgt"
    fi
done
#!/bin/bash
t=(x "$@")
i=1; while [ $i -lt $# ]; do
  echo cp ${t[$i]} ${t[$#]}
  i=$(($i + 1))
done

You don't need any bash-specific features:

eval last=\${$#}
while [ $# -gt 1 ]; do
  echo "cp $1 $last"
  shift
done

You can do this directly without writing a script using xargs :

echo source1 source2 | tr "\n" "\0" | tr " " "\0" | 
   xargs --verbose -0 -I{} cp {} dest

Use this to extract the last parameter:

eval tgt=\\$$#

then just process the same way you are and when you hit $# just exit the loop

Here's the whjole script:

eval tgt=\$$#

for src in $@
do
 if [ "$src" == "$tgt" ];
 then
    exit
fi
echo cp $src $tgt
done

Pretty simple if you ask me!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM