In bash, I can loop over all arguments, $#.
Is there a way to get the index of the current argument? (So that I can refer to the next one or the previous one.)
You can loop over the argument numbers, and use indirect expansion (${!argnum})to get the arguments from that:
for ((i=1; i<=$#; i++)); do
next=$((i+1))
prev=$((i-1))
echo "Arg #$i='${!i}', prev='${!prev}', next='${!next}'"
done
Note that $0 (the "previous" argument to $1) will be something like "-bash", while the "next" argument after the last will come out blank.
Not exactly as you specify, but you can iterate over the arguments a number of different ways.
For example:
while test $# -gt 0
do
echo $1
shift
done
Pretty simple to copy the positional params to an array:
$ set -- a b c d e # set some positional parameters
$ args=("$#") # copy them into an array
$ echo ${args[1]} # as we see, it's zero-based indexing
b
And, iterating:
$ for ((i=0; i<${#args[#]}; i++)); do
echo "$i ${args[i]} ${args[i-1]} ${args[i+1]}"
done
0 a e b
1 b a c
2 c b d
3 d c e
4 e d
Related
I have a script where the user can add as many arguments as he would like (numbers).
The script will sum all the numbers beside the last number - The last number (argument) is the number that I need to divide by
For example:
./test.sh 2 2 6 5
This will sum the first 3 numbers (2+2+6) and divide the answer by 5 (the last argument)
How can I use the last argument? Echo ????
How can I move loop the first arguments besides the last one – I would like that all 3 arguments will be added to an array and I can loop it
Please note that the number of arguments can be changed
How can I use the last argument? Echo ????
Granting $# > 0, you can use "${!#}".
How can I move loop the first arguments besides the last one – I would
like that all 3 arguments will be added to an array and I can loop it
Again granting $# > 0, you can refer to "${#:1:$# - 1}".
Read the Arrays section in the bash manual to know how to properly expand arrays.
I also recommend learning how quoting works and knowing the dangers of unwanted word splitting and globbing.
Shortly (with bashisms)
As this question is tagged integer-arithmetic and bash:
Here is a small and efficient script:
#!/bin/bash
addVals=${*: 1 : $# - 1}
declare -i intResult=" ( ${addVals// /+} ) / ${#: -1} "
echo $intResult
But there's no loop...
Long answer
How can I use the last argument? Echo ????
You could make your tries in command line:
set -- 2 2 6 5
Then
echo $#
2 2 6 5
echo ${*: 3}
6 5
echo ${*: -1}
5
echo ${*: 1 : -1}
bash: -1: substring expression < 0
echo $#
4
echo ${*: 1 : $# -1}
2 2 6
Ok, then
someVar=${*: 1 : $# -1}
echo ${someVar// /:SpaceReplacment:}
2:SpaceReplacment:2:SpaceReplacment:6
so
declare -i result
result=" ( ${someVar// /+} ) / ${*: -1} "
echo $result
2
How can I move loop the first arguments besides the last one – I would like that all 3 arguments will be added to an array and I can loop it
Still forward, under command line...
someArray=("${#: 1: $# -1 }")
Use declare -p to show $someArray's content:
declare -p someArray
declare -a someArray=([0]="2" [1]="2" [2]="6")
Then
declare -i mySum=0
for i in "${someArray[#]}";do
mySum+=i
done
echo $mySum
10
echo $(( mySum / ${*: -1} ))
2
Please note that the number of arguments can be changed
Please note:
Using double quotes allow processing of strings containing spaces:
set -- foo bar 'foo bar baz'
echo ${2}
bar
echo ${*: $# }
foo bar baz
Difference betweeen use of "$#" (array to array) and "$*" (array to string)
set -- foo bar 'foo bar' 'foo bar baz'
If I take 3 first elements:
someArray=("${#: 1: $# -1 }")
declare -p someArray
declare -a someArray=([0]="foo" [1]="bar" [2]="foo bar")
But
someArray=("${*: 1: $# -1 }")
declare -p someArray
declare -a someArray=([0]="foo bar foo bar")
There are about a thousand ways of doing this. As you would like to make use of integer arithmetic, you can do the following in bash
A short semi-cryptic version would be:
IFS=+
echo $(( ( ${*} - ${#:-1} ) / ${#:-1} ))
Here we make use of the difference between "${*}" and "${#}" to perform the sum by setting IFS=+ (See What is the difference between "$#" and "$*" in Bash?)
A long classic approach would be:
for i in "$#"; do ((s+=i)); done
echo $(( (s-${#:-1})/${#:-1} ))
It's easier to sum all terms and subtract the last term afterwards
in bash,now i have two strings,one is 'a b c' while another is '1 2 3'
any good way to combine them to 'a=1 b=2 c=3'
I tried string to array and combined them.but if i don't know the IFS?
IFS=' ' read -r -a array1 <<< "$upvote_count"
IFS=' ' read -r -a array0 <<< "$qids"
tLen=${#array[#]}
for (( i=0; i<${tLen}; i++ ));
do
echo "${array0[$i]}"" ""${array1[$i]}">>a.txt
done
You can create arrays from each string[1] and then use eval to create the variables with names from string 1 and values from string 2 by looping over each array element and evaluating array1[i]=array2[i] (pseudocode). A short script would look like the following:
#!/bin/bash
v1="a b c" ## original string variables
v2="1 2 3"
ar1=( $(echo $v1) ) ## create arrays 1 & 2 from strings
ar2=( $(echo $v2) )
for ((i=0; i<${#ar1[#]}; i++)); do
eval "${ar1[i]}=${ar2[i]}" ## eval to create variables
done ## (be careful with eval)
printf "a=%s\nb=%s\nc=%s\n" $a $b $c ## confirm
Output
$ bash evalabc.sh
a=1
b=2
c=3
You would want to add validations that the you have the same number of elements in each array, that the elements of the first don't begin with numbers, etc.. and that they do not contain anything that would be harmful when you run eval!
As noted in the comment of the script, take great care in using eval (or avoid it altogether) because it will do exactly what you tell it to do. You would not want to have, e.g. a=sudo rm, b="-rf", c=/* and then eval "$a $b $c" -- very bad things can happen.
Footnotes:
[1] (adjust IFS as needed - not needed for space separation)
Give this tested version a try:
unset letters; unset figures; IFS=' ' read -r -a letters <<< "a b c" ; \
IFS=' ' read -r -a figures <<< '1 2 3' ; \
for i in "${!letters[#]}" ; do \
printf "%s=%s\n" ${letters[i]} ${figures[i]}; \
done
a=1
b=2
c=3
A general method that works in any Bourne shell, (not just bash): Use a for loop for one list (a b c), match it up with piped input for the second list (1 2 3), produce a string of shell code, and eval that.
eval $(seq 3 | for f in a b c ; do read x ; echo $f=$x ; done) ;
echo $a $b $c
which prints:
1 2 3
eval is needed because variables assigned in a loop are forgotten once the loop is over.
Caution: never let eval execute unknown code. If either list contained unwanted shell code delimiters or commands, further parsing would be necessary, i.e. a prophylactic pipe after '; done'.
Applied to the specific example in the starting question, with two strings, containing space separated lists, we get:
qids="a b c" # our variable names
unset $qids # clear them if need be
upvote_count="1 2 3" # the values to be assigned
# generate code to assign the names list to the values list,
# via a 'for' loop, the index $var_name is for the $qids,
# and assign $var_name to each $upvote_count value which we pipe in.
# Since an assignment can't leave the loop, we 'echo'
# the code for assignment, and 'eval' that code after the loop
# is done.
eval $(
echo "${upvote_count}" |
tr ' ' '\n' |
for var_name in $qids ; do
read value
echo "$var_name=$value"
done
)
# The loop is done, so test if the code worked:
for var_name in $qids
do
echo -n $var_name=
eval echo \$$var_name
done
...which outputs:
a=1
b=2
c=3
I want to merge all files into one. Here, the last argument is the destination file name.
I want to take last argument and then in loop stop before last arguments.
Here code is given that I want to implement:
echo "No. of Argument : $#"
for i in $* - 1
do
echo $i
cat $i >> last argument(file)
done
How to achieve that?
Using bash:
fname=${!#}
for a in "${#:1:$# - 1}"
do
echo "$a"
cat "$a" >>"$fname"
done
In bash, the last argument to a script is ${!#}. So, that is where we get the file name.
bash also allows selecting elements from an array. To start with a simple example, observe:
$ set -- a b c d e f
$ echo "${#}"
a b c d e f
$ echo "${#:2:4}"
b c d e
In our case, we want to select elements from the first to the second to last. The first is number 1. The last is number $#. We want to select all but the last. WE thus want $# - 1 elements of the array. Therefore, to select the arguments from the first to the second to last, we use:
${#:1:$# - 1}
A POSIX-compliant method:
eval last_arg=\$$#
while [ $# -ne 1 ]; do
echo "$1"
cat "$1" >> "$last_arg"
shift
done
Here, eval is safe, because you are only expanding a read-only parameter in the string that eval will execute. If you don't want to unset the positional parameters via shift, you can iterate over them, using a counter to break out of the loop early.
eval last_arg=\$$#
i=1
for arg in "$#"; do
echo "$arg"
cat "$arg" >> "$last_arg"
i=$((i+1))
if [ "$i" = "$#" ]; then
break
fi
done
I'm trying to list script's argument by printf function. Arguments are counted by iteration of $i. What should be in printf function?
I need something like
eval echo \$$i
but in printf function.
Edit: Have while cycle with iteration of $i and among other code, I have
printf "%s" $i
But, instead of $i, I need some code, that shows me value of argument.
In my case, it is name of file, and I need list them. One file in one iteration.
As noted in a comment, you normally do that with a loop such as:
i=1
for arg in "$#"
do
echo "$i: [$arg]"
((i++))
done
(If echo isn't allowed, use printf "%s\n" … where the … is whatever would have followed echo.)
You might also use indirect expansion to avoid the use of eval:
for i in 1 2 3 4; do echo "$i: [${!i}]"; done
You can generalize that with:
for i in $(seq 1 $#); do echo "$i: [${!i}]"; done
or
for ((i = 1; i <= $#; i++)); do echo "$i: [${!i}]"; done
For example, given:
set -- a b 'c d' ' e f '
all the loops produce the output:
1: [a]
2: [b]
3: [c d]
4: [ e f ]
The square brackets are merely there to delimit the argument values; it allows you to see the trailing blanks on the fourth line of output.
You might also be able to use:
printf "[%s]\n" "$#"
to get:
[a]
[b]
[c d]
[ e f ]
It is not very clear what you are asking for, but this will list the arguments passed to the script:
while [ $# -gt 0 ]; do
printf "%s\n" "$1"
shift
done
I guess what you are asking for is how to make the indirect reference to positional parameter i (i containing the position):
print "%s\n" ${!i}
To get your arguments in such a way that they can be fed back into the shell with the exact same value, the following bash extension can be used:
printf '%q ' "$#"; printf '\n'
This works even for rather unusual cases. Let's say that one of your arguments contains a newline:
./your-script 'hello
world' 'goodbye world'
This will be represented in the printf output:
$'hello\nworld' goodbye\ world
...with something you can use again in the shell:
$ echo $'hello\nworld' goodbye\ world
hello
world goodbye world
I have a bash script called foo with variable number of arguments, with the first one being a required one, i.e.:
foo a1 b2 b3 b4 ...
I understand that in bash $1 will get me argument a1, but is there a way to get all the rest of the arguments? $# or $* seem to get me all the arguments including a1.
Slice the $# array.
echo "${#:2}"
You can use shift command for that. That will remove $1 and you can access the rest of arguments starting with $1.
#!/bin/sh
echo $*
shift
echo $*
shift will shift all the parameters, running the previous example would give:
$ test_shift.sh a b c d e
a b c d e
b c d e
./foo.sh 1 2 3 4
#!/bin/bash
echo $1;
echo $2;
echo $3;
echo $4;
Will output:
1
2
3
4