Index arguments in zsh - arguments

With zsh I can get the fifth argument simply with $5. But what if 5 is a variable? I've come up with this way to print out the first five arguments by indexing (as opposed to just echo $1 $2 $3 $4 $5):
for i in {1..5}
do
echo $(eval echo "\$$i")
done
But surely there must be a better way?
I know that there is much simpler ways to loop through all arguments. In my particular case I want to loop through the argument list backward. Help with that would be appreciated as well.

To iterate through the positional parameters, just use a for loop:
for x; do echo $x; done
To iterate through them in reverse:
for x in "${(Oa)#}"; do echo $x ; done
To reverse the parameters:
set "${(Oa)#}"

In zsh (and zsh only, apparently) the # is an array. Practically you can use it like this:
for i in {1..5}
do
echo $#[i]
done
As for looping backwards I guess you can combine for i in {$#..1} with $#[i] but there might be a better way.

Related

Bash script has several parameters always have random order how to put them in correct order always

My script is called by a program that generates argument randomly such as
input=12 output=14 destinationroute=10.0.0.0
and then calls my script with the generated arguments:
./getroute.sh input=12 output=14 destinationroute=10.0.0.0
Inside the script is like:
#!/bin/bash
input=$1
output=$2
destinationroute=$3
...
The program always calls arguments in random order (ex. input=12 output=14 or output=14 input=12), and I can't change the program.
Is there any way to recognize the correct parameters and put them in their proper place.
Don't rely on order if they aren't in order. Just iterate over the arguments, look at which patterns they match, and assign to a variable appropriately:
for arg; do # default for a for loop is to iterate over "$#"
case $arg in
'input='*) input=${arg#*=} ;;
'output='*) output=${arg#*=} ;;
'destinationroute='*) destinationroute=${arg#*=} ;;
esac
done
If, for some reason, you really wanted to update $1, $2, and $3, though, you can do that by putting the following code after the above loop:
set -- "$input" "$output" "$destinationroute"
you need to call your function differently; For exmple:
./getroute.sh -i 12 -o 14 -d 10.0.0.0
Then inside your script use getopt to read the variables.
Edit:
My scripting knowledge is not strong; therefore, there should be a better way to do it.
As you don't have access to the Program, you can add some lines inside your script to get the inputs; for example:
input=`echo $* | grep -E -o "input=[0-9]{2}" | awk -F"=" {'print$2'}`
You can do the same thing for other variables.

For loop with an argument based range

I want to run certain actions on a group of lexicographically named files (01-09 before 10). I have to use a rather old version of FreeBSD (7.3), so I can't use yummies like echo {01..30} or seq -w 1 30.
The only working solution I found is printf "%02d " {1..30}. However, I can't figure out why can't I use $1 and $2 instead of 1 and 30. When I run my script (bash ~/myscript.sh 1 30) printf says {1..30}: invalid number
AFAIK, variables in bash are typeless, so how can't printf accept an integer argument as an integer?
Bash supports C-style for loops:
s=1
e=30
for i in ((i=s; i<e; i++)); do printf "%02d " "$i"; done
The syntax you attempted doesn't work because brace expansion happens before parameter expansion, so when the shell tries to expand {$1..$2}, it's still literally {$1..$2}, not {1..30}.
The answer given by #Kent works because eval goes back to the beginning of the parsing process. I tend to suggest avoiding making habitual use of it, as eval can introduce hard-to-recognize bugs -- if your command were whitelisted to be run by sudo and $1 were, say, '$(rm -rf /; echo 1)', the C-style-for-loop example would safely fail, and the eval example... not so much.
Granted, 95% of the scripts you write may not be accessible to folks executing privilege escalation attacks, but the remaining 5% can really ruin one's day; following good practices 100% of the time avoids being in sloppy habits.
Thus, if one really wants to pass a range of numbers to a single command, the safe thing is to collect them in an array:
a=( )
for i in ((i=s; i<e; i++)); do a+=( "$i" ); done
printf "%02d " "${a[#]}"
I guess you are looking for this trick:
#!/bin/bash
s=1
e=30
printf "%02d " $(eval echo {$s..$e})
Ok, I finally got it!
#!/bin/bash
#BSD-only iteration method
#for day in `jot $1 $2`
for ((day=$1; day<$2; day++))
do
echo $(printf %02d $day)
done
I initially wanted to use the cycle iterator as a "day" in file names, but now I see that in my exact case it's easier to iterate through normal numbers (1,2,3 etc.) and process them into lexicographical ones inside the loop. While using jot, remember that $1 is the numbers amount, and the $2 is the starting point.

Skip items in bash when in exclusion array

I have a script that loops over database names and if the name of the current database is in my exclusion array I want to skip it. How would I accomplish this in bash?
excluded_databases=("template1" "template0")
for database in $databases
do
if ...; then
# perform something on the database...
fi
done
You can do it by testing each name in turn, but you might be better off filtering the list in one operation. (The following assumes that no name in $databases contains whitespace, which is implicit given your for loop).
for database in $(printf %s\\n $databases |
grep -Fvx "${excluded_databases[#]/#/-e}"); do
# something
done
Explanation of the idioms:
printf %s\\n ... prints each of its arguments on a single line.
grep -Fvx searchs for exact matches (-F) of the whole line (-x) and inverts the match result (-v).
"${array[#]/#/-e}" prepends -e to each element of the array array, which is useful when you need to provide each element of the array as a (repeated) command-line option to a utility. In this case, the utility is grep and the -e flag is used to provide a match pattern.
I've been criticized in the past for printf %s\\n -- some people prefer printf '%s\n' -- but I find the first one easier to type. YMMV.
As a comment, it seems like it would be better to make $databases an array as well as $excluded_databases, which would allow for names including whitespace. The printf | grep solution still doesn't allow newlines in names; it's complicated to work around that. If you were to make that change, you'd only need to change the printf to printf %s\\n "${databases[#]}".
You can use this condition to check for presence of an element in an array:
if [[ "${excluded_databases[#]/$database}" == "${excluded_databases[#]}" ]]
Another option using case:
case "${excluded_databases[#]}" in *"$database"*) echo "found in array" ;; esac
If you are using bash 4 or greater then using an associative array will help you here.
declare -A excluded_databases=(["template1"]=1 ["template0"]=1)
for database in $databases
do
if [ -z "${excluded_databases[$database]}" ]; then
continue
fi
# ... do something with $database
done

print a variable using another

I have my script setup like this, i want to print switch names using loop.
swn1="something"
swn2="somethingelse"
for (( i=1; i<="$ii"; i++ ))
do
echo "$swn$i "
done
I have searched and searched but no luck, can anyone help me how to print swn using for loop ?
Sounds like you want an array...
swn=("something" "something else")
for i in "${swn[#]}"
do
echo $i
done
The solution for that is variable indirection.
swn1="something"
swn2="somethingelse"
for (( i=1; i<="$ii"; i++ ))
do
var="swn$i"
echo "${!var}"
done
Although normally you could solve your problem with arrays, but the way to print a variable using another is through it.
As explained in the bash manual:
If the first character of parameter is an exclamation point (!), a
level of variable indirection is introduced. Bash uses the value of
the variable formed from the rest of parameter as the name of the
variable; this variable is then expanded and that value is used in the
rest of the substitution, rather than the value of parameter itself.
This is known as indirect expansion. The exceptions to this are the
expansions of ${!prefix} and ${!name[#]}. The
exclamation point must immediately follow the left brace in order to
introduce indirection.
You can't do this. swn1 is one token, swn2 is another. It looks like you want an array, and if you want an array in a bash script you either want to process the values as separate lines in a file, or switch to using Perl.
Separate lines:
echo "something" > swnfile;
echo "somethingelse" >> swnfile;
cat file | while read line; do
echo "$line";
done

Generating variations with bash

Looking for en nicer solution for the next code:
for i in a b c
do
for j in A B C
do
for k in 1 2 3
do
echo "$i$j$k"
done
done
done
Sure is here some simpler solution.
This is a bit simpler
echo {a,b,c}{A,B,C}{1,2,3}
or if want one per line, so
echo {a,b,c}{A,B,C}{1,2,3} | xargs -n1
BTW, you can use the above bracer expansion for example saving keyboard typing for example when need make backup files, like:
cp /some/long/path/And_very-ugly-fileName{,.copy}
will make the /some/long/path/And_very-ugly-fileName.copy without second filename typing.
Use a brace expansion:
echo {a,b,c}{A,B,C}{1,2,3}
This will print all possible combinations between the sets {a,b,c}, {A,B,C}, and {1,2,3}.
Or even simpler, using ranges:
echo {a..c}{A..C}{1..3}
And if you want to print each combination per line, use a for-loop:
for i in {a..c}{A..C}{1..3}; do echo $i; done

Resources