John Zwinck
John Zwinck

Reputation: 249303

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 "a b c d" (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

Upvotes: 0

Views: 2021

Answers (8)

Idelic
Idelic

Reputation: 15582

You don't need any bash-specific features:

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

Upvotes: 0

Amro
Amro

Reputation: 124563

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

Upvotes: 0

DigitalRoss
DigitalRoss

Reputation: 146093

#!/bin/bash
t=(x "$@")
i=1; while [ $i -lt $# ]; do
  echo cp ${t[$i]} ${t[$#]}
  i=$(($i + 1))
done

Upvotes: 0

Gordon Davisson
Gordon Davisson

Reputation: 125858

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

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

Upvotes: 2

ennuikiller
ennuikiller

Reputation: 46965

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!

Upvotes: -1

Lance Rushing
Lance Rushing

Reputation: 7640

/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

Upvotes: 5

Hai Vu
Hai Vu

Reputation: 40733

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 a b c d 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 a b c d then ${BASH_ARGV[0]} == 'd', ... ${BASH_ARGV[3]} == 'a':

last_arg=${BASH_ARGV[0]}

I hope this helps.

Upvotes: 1

Dennis Williamson
Dennis Williamson

Reputation: 360153

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

Upvotes: 1

Related Questions