How to iterate over positional parameters in a Bash script? - bash

Where am I going wrong?
I have some files as follows:
filename_tau.txt
filename_xhpl.txt
filename_fft.txt
filename_PMB_MPI.txt
filename_mpi_tile_io.txt
I pass tau, xhpl, fft, mpi_tile_io and PMB_MPI as positional parameters to script as follows:
./script.sh tau xhpl mpi_tile_io fft PMB_MPI
I want grep to search inside a loop, first searching tau, xhpl and so on..
point=$1 #initially points to first parameter
i="0"
while [$i -le 4]
do
grep "$str" ${filename}${point}.txt
i=$[$i+1]
point=$i #increment count to point to next positional parameter
done

Set up your for loop like this. With this syntax, the loop iterates over the positional parameters, assigning each one to 'point' in turn.
for point; do
grep "$str" ${filename}${point}.txt
done

There is more than one way to do this and, while I would use shift, here's another for variety. It uses Bash's indirection feature:
#!/bin/bash
for ((i=1; i<=$#; i++))
do
grep "$str" ${filename}${!i}.txt
done
One advantage to this method is that you could start and stop your loop anywhere. Assuming you've validated the range, you could do something like:
for ((i=2; i<=$# - 1; i++))
Also, if you want the last param: ${!#}

See here, you need shift to step through positional parameters.

Try something like this:
# Iterating through the provided arguments
for ARG in $*; do
if [ -f filename_$ARG.txt]; then
grep "$str" filename_$ARG.txt
fi
done

args=$#;args=${args// /,}
grep "foo" $(eval echo file{$args})

Related

sed not parsing variable

I'm stuck with a bash script here...
I have variables:
hostname1="sxxx" hostname2="vbbbb" hostname3="sggg" hostname4="aaa" ...
I'm trying to change the 12th line of every files in a folder with the host-name variables.
The files are server1.txt server2.txt server3.txt server4.txt ...
I'm trying to do this with a while loop:
i=1
imax=1
while [[ $i -le 20 ]]
do
sed -i "12s/.*/$hostname$imax/" server$((imax)).txt
(( i++ ))
(( imax++ ))
if [[ imax -eq 21 ]]
then
imax=1
fi
done
what I want to do with sed is to concatenate the word host-name with imax and then use it as variable.
Maybe with this I'm clear enough:
$hostname=hostname$imax; //for exammple
sed -i "12s/.*/$hostname/" server$((imax)).txt // i need here the variable $hostname to have the content "sxxx"
You achieve this by using indirect parameter expansion,
${!var}
Here, var - is a dynamically generated variable name.
Try this:
sed -i "12s/.*/${!hostname}/" server$((imax)).txt
Example:
$ hostname1="sat"
$ i=1
$ hostval="hostname$i"
$ echo ${!hostval}
sat
I'd use the following. Use change, instead of switch in sed. Then strong quote my sed, and unquote my variables.
for i in {1..20}; do
eval h='$'hostname$i
sed -i '12c'$h'' server$i.txt
done
Bash 3 and over supports number ranges in a for looop, easier than your while loop. I also have no idea what you are doing with imax instead of just i, b/c you exit at 20, but change imax value to 1... which it will never use.
edit: b/c I misread
Basically, your problem comes from a variable interpretation.
Try using sed >=4.2.2, should work with your code.

Shell substitution variable, err in [output]

looking for some help here. I am seeing the below issue
y=1
j$y=`cat /home/devteam/auppu/new_ig_1|head -n $y`
ksh: j1=5555555555555555: not found
i have no issue when i cat on the file,like below
cat /home/devteam/auppu/new_ig_1|head -n $y
5555555555555555
The simplest way to do this is using an indexed array, like so:
y=1
j[$y]=`cat /home/devteam/auppu/new_ig_1|head -n $y`
echo ${j[$y]}
This way you can store multiple invocations of the cat command in your loop into the associative array referenced by the j variable.
You might have to do something like
y=1
x=j${y}
x=`cat /home/devteam/auppu/new_ig_1|head -n $y`
echo $x
You would need to create an intermediate variable (x in this case) and then assign to it the results of your cat command

BASH - Refer to parameters using variable

I was trying to make a small script that calculates a binary number to decimal. It works like this:
-Gets '1' or '0' digits as SEPARATE parameters. e.g. "./bin2dec 1 0 0 0 1 1 1".
-For each parameter digit: if it is '1', multiplies it with the corresponding power of 2 (in the above case, the most left '1' will be 64), then adds it in a 'sum' variable.
Here's the code (it is wrong in the noted point):
#!/bin/bash
p=$((2**($#-1))) #Finds the power of two for the first parameter.
sum=0 #The sum variable, to be used for adding the powers of two in it.
for (( i=1; i<=$#; i++ )) #Counts all the way from 1, to the total number of parameters.
do
if [ $i -eq 1 ] # *THIS IS THE WRONG POINT* If parameter content equals '1'...
then
sum=$(($sum+$p)) #...add the current power of two in 'sum'.
fi
p=$(($p/2)) #Divides the power with 2, so to be used on the next parameter.
done
echo $sum #When finished show the 'sum' content, which is supposed to be the decimal equivalent.
My question is in the noted point (line #10, including blank lines). There, I’m trying to check if EACH parameter's content equals 1. How can I do this using a variable?
For example, $1 is the first parameter, $2 is the 2nd and so on. I want it to be like $i, where 'i' is the variable that is increased by one each time so that it matches the next parameter.
Among other things, I tried this: '$(echo "$"$i)' but didn't work.
I know my question is complicated and I tried hard to make it as clear as I could.
Any help?
How about this?
#!/usr/bin/bash
res=0
while [[ $# -gt 0 ]]
do
res=$(($res * 2 + $1))
shift
done
echo $res
$ ./bin2dec 1 0 0 1
9
shift is a command that moves every parameter passed to the script to the left, decreasing it's value by 1, so that the value of $2 is now at $1 after a shift. You can actually a number with shift as well, to indicate how far to shift the variables, so shift is like shift 1, and shift 2 would give $1 the value that used to be at $3.
How about this one?
#!/bin/bash
p=$((2**$#))
while(($#)); do
((sum+=$1*(p/=2)))
shift
done
echo "$sum"
or this one (that goes in the other direction):
#!/bin/bash
while(($#)); do
((sum=2*sum+$1))
shift
done
echo "$sum"
Note that there are no error checkings.
Please consider fedorqui's comment: use echo $((2# binary number )) to convert from binary to decimal.
Also note that this will overflow easily if you give too many arguments.
If you want something that doesn't overflow, consider using dc:
dc <<< "2i101010p"
(setting input radix to 2 with 2i and putting 101010 on the stack and printing it).
Or if you like bc better:
bc <<< "ibase=2;101010"
Note that these need the binary number to be entered as one argument, and not as you wanted with all digits separated. If you really need all digits to be separated, you could also use these funny methods:
Pure Bash.
#!/bin/bash
echo $((2#$(printf '%s' "$#")))
With dc.
#!/bin/bash
dc <<< "2i$(printf '%s' "$#")p"
With bc.
#!/bin/bash
bc <<< "ibase=2;$(printf '%s' "$#")"
Abusing IFS, with dc.
#!/bin/bash
( IFS=; echo "2i$*p" ) | dc
Abusing IFS, with bc.
#!/bin/bash
( IFS=; echo "ibase=2;$*" ) | bc
So we still haven't answered your question (the one in your comment of the OP): And to find out if and how can we refer to a parameter using a variable. It's done in Bash with indirect expansion. You can read about it in the Shell Parameter Expansion section of the Bash reference manual. The best thing is to show you in a terminal:
$ a="hello"
$ b=a
$ echo "$b"
a
$ echo "${!b}"
hello
neat, eh? It's similar with positional parameters:
$ set param1 param2 param3
$ echo "$1, $2, $3"
param1, param2, param3
$ i=2
$ echo "$i"
2
$ echo "${!i}"
param2
Hope this helps!
To reference variables indirectly by name, use ${!name}:
i=2
echo "${!i} is now equivalent to $2"
Try this one also:
#!/bin/bash -
( IFS=''; echo $((2#$*)) )

Iterate through parameters skipping the first

Hi i have the following:
bash_script parm1 a b c d ..n
I want to iterate and print all the values in the command line starting from a, not from parm1
You can "slice" arrays in bash; instead of using shift, you might use
for i in "${#:2}"
do
echo "$i"
done
$# is an array of all the command line arguments, ${#:2} is the same array less the first element. The double-quotes ensure correct whitespace handling.
This should do it:
#ignore first parm1
shift
# iterate
while test ${#} -gt 0
do
echo $1
shift
done
This method will keep the first param, in case you want to use it later
#!/bin/bash
for ((i=2;i<=$#;i++))
do
echo ${!i}
done
or
for i in ${*:2} #or use $#
do
echo $i
done
Another flavor, a bit shorter that keeps the arguments list
shift
for i in "$#"
do
echo $i
done
You can use an implicit iteration for the positional parameters:
shift
for arg
do
something_with $arg
done
As you can see, you don't have to include "$#" in the for statement.

Getting the last argument passed to a shell script

$1 is the first argument.
$# is all of them.
How can I find the last argument passed to a shell
script?
This is Bash-only:
echo "${#: -1}"
This is a bit of a hack:
for last; do true; done
echo $last
This one is also pretty portable (again, should work with bash, ksh and sh) and it doesn't shift the arguments, which could be nice.
It uses the fact that for implicitly loops over the arguments if you don't tell it what to loop over, and the fact that for loop variables aren't scoped: they keep the last value they were set to.
$ set quick brown fox jumps
$ echo ${*: -1:1} # last argument
jumps
$ echo ${*: -1} # or simply
jumps
$ echo ${*: -2:1} # next to last
fox
The space is necessary so that it doesn't get interpreted as a default value.
Note that this is bash-only.
The simplest answer for bash 3.0 or greater is
_last=${!#} # *indirect reference* to the $# variable
# or
_last=$BASH_ARGV # official built-in (but takes more typing :)
That's it.
$ cat lastarg
#!/bin/bash
# echo the last arg given:
_last=${!#}
echo $_last
_last=$BASH_ARGV
echo $_last
for x; do
echo $x
done
Output is:
$ lastarg 1 2 3 4 "5 6 7"
5 6 7
5 6 7
1
2
3
4
5 6 7
The following will work for you.
# is for array of arguments.
: means at
$# is the length of the array of arguments.
So the result is the last element:
${#:$#}
Example:
function afunction{
echo ${#:$#}
}
afunction -d -o local 50
#Outputs 50
Note that this is bash-only.
Use indexing combined with length of:
echo ${#:${##}}
Note that this is bash-only.
Found this when looking to separate the last argument from all the previous one(s).
Whilst some of the answers do get the last argument, they're not much help if you need all the other args as well. This works much better:
heads=${#:1:$#-1}
tail=${#:$#}
Note that this is bash-only.
This works in all POSIX-compatible shells:
eval last=\${$#}
Source: http://www.faqs.org/faqs/unix-faq/faq/part2/section-12.html
Here is mine solution:
pretty portable (all POSIX sh, bash, ksh, zsh) should work
does not shift original arguments (shifts a copy).
does not use evil eval
does not iterate through the whole list
does not use external tools
Code:
ntharg() {
shift $1
printf '%s\n' "$1"
}
LAST_ARG=`ntharg $# "$#"`
From oldest to newer solutions:
The most portable solution, even older sh (works with spaces and glob characters) (no loop, faster):
eval printf "'%s\n'" "\"\${$#}\""
Since version 2.01 of bash
$ set -- The quick brown fox jumps over the lazy dog
$ printf '%s\n' "${!#} ${#:(-1)} ${#: -1} ${#:~0} ${!#}"
dog dog dog dog dog
For ksh, zsh and bash:
$ printf '%s\n' "${#: -1} ${#:~0}" # the space beetwen `:`
# and `-1` is a must.
dog dog
And for "next to last":
$ printf '%s\n' "${#:~1:1}"
lazy
Using printf to workaround any issues with arguments that start with a dash (like -n).
For all shells and for older sh (works with spaces and glob characters) is:
$ set -- The quick brown fox jumps over the lazy dog "the * last argument"
$ eval printf "'%s\n'" "\"\${$#}\""
The last * argument
Or, if you want to set a last var:
$ eval last=\${$#}; printf '%s\n' "$last"
The last * argument
And for "next to last":
$ eval printf "'%s\n'" "\"\${$(($#-1))}\""
dog
If you are using Bash >= 3.0
echo ${BASH_ARGV[0]}
For bash, this comment suggested the very elegant:
echo "${#:$#}"
To silence shellcheck, use:
echo ${*:$#}
As a bonus, both also work in zsh.
shift `expr $# - 1`
echo "$1"
This shifts the arguments by the number of arguments minus 1, and returns the first (and only) remaining argument, which will be the last one.
I only tested in bash, but it should work in sh and ksh as well.
I found #AgileZebra's answer (plus #starfry's comment) the most useful, but it sets heads to a scalar. An array is probably more useful:
heads=( "${#: 1: $# - 1}" )
tail=${#:${##}}
Note that this is bash-only.
Edit: Removed unnecessary $(( )) according to #f-hauri's comment.
A solution using eval:
last=$(eval "echo \$$#")
echo $last
If you want to do it in a non-destructive way, one way is to pass all the arguments to a function and return the last one:
#!/bin/bash
last() {
if [[ $# -ne 0 ]] ; then
shift $(expr $# - 1)
echo "$1"
#else
#do something when no arguments
fi
}
lastvar=$(last "$#")
echo $lastvar
echo "$#"
pax> ./qq.sh 1 2 3 a b
b
1 2 3 a b
If you don't actually care about keeping the other arguments, you don't need it in a function but I have a hard time thinking of a situation where you would never want to keep the other arguments unless they've already been processed, in which case I'd use the process/shift/process/shift/... method of sequentially processing them.
I'm assuming here that you want to keep them because you haven't followed the sequential method. This method also handles the case where there's no arguments, returning "". You could easily adjust that behavior by inserting the commented-out else clause.
For tcsh:
set X = `echo $* | awk -F " " '{print $NF}'`
somecommand "$X"
I'm quite sure this would be a portable solution, except for the assignment.
After reading the answers above I wrote a Q&D shell script (should work on sh and bash) to run g++ on PGM.cpp to produce executable image PGM. It assumes that the last argument on the command line is the file name (.cpp is optional) and all other arguments are options.
#!/bin/sh
if [ $# -lt 1 ]
then
echo "Usage: `basename $0` [opt] pgm runs g++ to compile pgm[.cpp] into pgm"
exit 2
fi
OPT=
PGM=
# PGM is the last argument, all others are considered options
for F; do OPT="$OPT $PGM"; PGM=$F; done
DIR=`dirname $PGM`
PGM=`basename $PGM .cpp`
# put -o first so it can be overridden by -o specified in OPT
set -x
g++ -o $DIR/$PGM $OPT $DIR/$PGM.cpp
The following will set LAST to last argument without changing current environment:
LAST=$({
shift $(($#-1))
echo $1
})
echo $LAST
If other arguments are no longer needed and can be shifted it can be simplified to:
shift $(($#-1))
echo $1
For portability reasons following:
shift $(($#-1));
can be replaced with:
shift `expr $# - 1`
Replacing also $() with backquotes we get:
LAST=`{
shift \`expr $# - 1\`
echo $1
}`
echo $LAST
echo $argv[$#argv]
Now I just need to add some text because my answer was too short to post. I need to add more text to edit.
This is part of my copy function:
eval echo $(echo '$'"$#")
To use in scripts, do this:
a=$(eval echo $(echo '$'"$#"))
Explanation (most nested first):
$(echo '$'"$#") returns $[nr] where [nr] is the number of parameters. E.g. the string $123 (unexpanded).
echo $123 returns the value of 123rd parameter, when evaluated.
eval just expands $123 to the value of the parameter, e.g. last_arg. This is interpreted as a string and returned.
Works with Bash as of mid 2015.
To return the last argument of the most recently used command use the special parameter:
$_
In this instance it will work if it is used within the script before another command has been invoked.
#! /bin/sh
next=$1
while [ -n "${next}" ] ; do
last=$next
shift
next=$1
done
echo $last
Try the below script to find last argument
# cat arguments.sh
#!/bin/bash
if [ $# -eq 0 ]
then
echo "No Arguments supplied"
else
echo $* > .ags
sed -e 's/ /\n/g' .ags | tac | head -n1 > .ga
echo "Last Argument is: `cat .ga`"
fi
Output:
# ./arguments.sh
No Arguments supplied
# ./arguments.sh testing for the last argument value
Last Argument is: value
Thanks.
There is a much more concise way to do this. Arguments to a bash script can be brought into an array, which makes dealing with the elements much simpler. The script below will always print the last argument passed to a script.
argArray=( "$#" ) # Add all script arguments to argArray
arrayLength=${#argArray[#]} # Get the length of the array
lastArg=$((arrayLength - 1)) # Arrays are zero based, so last arg is -1
echo ${argArray[$lastArg]}
Sample output
$ ./lastarg.sh 1 2 buckle my shoe
shoe
Using parameter expansion (delete matched beginning):
args="$#"
last=${args##* }
It's also easy to get all before last:
prelast=${args% *}
$ echo "${*: -1}"
That will print the last argument
With GNU bash version >= 3.0:
num=$# # get number of arguments
echo "${!num}" # print last argument
Just use !$.
$ mkdir folder
$ cd !$ # will run: cd folder

Resources