Bash for loop fails when sequence end is a variable [duplicate] - bash

This question already has answers here:
How do I iterate over a range of numbers defined by variables in Bash?
(20 answers)
Variables in bash seq replacement ({1..10}) [duplicate]
(7 answers)
Closed 5 years ago.
#!/bin/sh
for i in {1..5}
do
echo "Welcome"
done
Would work, displays Welcome 5 times.
#!/bin/sh
howmany=`grep -c $1 /root/file`
for i in {1..$howmany}
do
echo "Welcome"
done
Doesn't work! howmany would equal 5 as that is what the output of grep -c would display. $1 is parameter 1 which is specific when running the script.
Any ideas?

Workarounds for not being able to use variables in a sequence brace expression:
If the intent is merely to iterate over numbers in a range - as in the OP's case - the best choice is not to use brace expansion, but instead use bash's C-style loop - see user000001's answer.
If the specific numbers aren't important and you simply need to execute a loop body a specified number of times, Cole Tierney's answer is an option.
If use of brace expansion is desired nonetheless:
If you do NOT need the numbers in the list to have a prefix or postfix, use the seq utility with an unquoted command substitution (small caveat: seq is NOT a POSIX utility, but it is widely available); e.g.
echo $(seq 3) -> 1 2 3; start number 1 implied
echo $(seq -f '%02.f' 3) -> 01 02 03 - zero-padded
echo $(seq 2 4) -> 2 3 4; explicit start and end numbers
echo $(seq 1 2 5) -> 1 3 5; custom increment (the 2 in the middle)
If you DO need the numbers in the list to have a prefix or postfix, you have several choices:
Use the seq utility with its -f option for providing a printf-style format string (as used above for zero-padding), or pure Bash workarounds based on eval (extra care needed!) or building an array in a loop, all of which are detailed in this answer.
You could also consider implementing the functionality generically, such as by writing a custom shell function or a custom script with utilities such as awk or perl.
Example of safe use of eval with variables driving a sequence brace expression:
The variables are validated beforehand, to make sure they contain decimal integers.
from=1 to=3 # sample values
# Ensure that $from and $to are decimal numbers and abort, if they are not.
(( 10#$from + 10#$to || 1 )) 2>/dev/null || { echo "Need decimal integers" >&2; exit 1; }
eval echo "A{$from..$to}" # -> 'A1 A2 A3'
General overview of brace expansion
The main purpose of brace expansion is to expand to a list of tokens with each token having an optional prefix and/or postfix; brace expansions must be unquoted and come in 2 flavors:
a fixed series (list) of comma-separated strings - variables supported
specifies and expands to a fixed number of tokens (2 or more); e.g.:
echo A{b,c,d} -> Ab Ac Ad, i.e., 3 tokens, as implied by the number of args.
echo {/,$HOME/}Library e.g., -> /Library /User/jdoe/Library
Variable references - and even globs - are supported, but note that they get expanded after brace expansion, in its result, in the course of normal evaluation.
a sequence expression (range) with .., typically numerical - variables NOT supported
expands to a variable number of tokens, driven by literal start and end points (for historical reasons, use of variables is NOT supported - see the comments on user000001's answer):
[rare] strings: only single English letters allowed; e.g. {a..c}
numbers: decimal integers only; e.g., {1..10}, {10..1}, {-1..2}
example with prefix and postfix: A{1..3}# -> A1# A2# A3#
broken example with variables: {$from..$to} # !! FAILS - $from and $to are interpreted as literals and therefore not recognized as either a single letter or a decimal integer - no brace expansion is performed (see below).
by contrast, using variables does work in zsh and ksh.
bash 4+ adds two features:
optional increment step value:
echo A{1..5..2} -> A1 A3 A5 - numbers incremented by 2
ability to zero-pad:
echo A{001..003} -> A001 A002 A003
An invalid brace expression is not expanded (treated like a regular unquoted string, with { and } treated as literals):
echo {} -> '{}' - invalid as a brace expr.: at least 2 ,-separated tokens needed
This allows the use of unquoted {} with find, for instance.
echo {1..$to} -> '{1..<value-of-$to>}' - invalid as a brace expr. in bash: variables not supported; however, valid in ksh and zsh.
(fish, by contrast, expands any {...} sequence; similarly, zsh has option BRACE_CCL (OFF by default) for expanding individual characters inside {..}, which effectively causes expansion of any nonempty {...} sequence.)

The brace expansion is evaluated before the variables are expanded. You need a c-style for loop instead:
for ((i=1;i<=howmany;i++))
do
echo "Welcome"
done

create a sequence to control your loop
for i in $(seq 1 $howmany); do
echo "Welcome";
done

The problem is that the "brace expansion" is performed before the "variable expansion"
for i in $(seq 1 $howmany)
works as #damienfrancois said, or, if you would like:
for i in $(eval echo "{$start..10}")
probably does, but don't use it for everyone's sanity.

You could also use a while loop:
while ((howmany--)); do
echo "Welcome"
done

We could also use eval in this case:
howmany=`grep -c $1 /root/file`
for i in $(eval echo {1..$howmany}); do
echo "Welcome"
done

Related

How can I use a variable within a ".." range, as in {1..${num_lanes}}? [duplicate]

This question already has answers here:
How do I iterate over a range of numbers defined by variables in Bash?
(20 answers)
Variables in bash seq replacement ({1..10}) [duplicate]
(7 answers)
Closed 5 years ago.
#!/bin/sh
for i in {1..5}
do
echo "Welcome"
done
Would work, displays Welcome 5 times.
#!/bin/sh
howmany=`grep -c $1 /root/file`
for i in {1..$howmany}
do
echo "Welcome"
done
Doesn't work! howmany would equal 5 as that is what the output of grep -c would display. $1 is parameter 1 which is specific when running the script.
Any ideas?
Workarounds for not being able to use variables in a sequence brace expression:
If the intent is merely to iterate over numbers in a range - as in the OP's case - the best choice is not to use brace expansion, but instead use bash's C-style loop - see user000001's answer.
If the specific numbers aren't important and you simply need to execute a loop body a specified number of times, Cole Tierney's answer is an option.
If use of brace expansion is desired nonetheless:
If you do NOT need the numbers in the list to have a prefix or postfix, use the seq utility with an unquoted command substitution (small caveat: seq is NOT a POSIX utility, but it is widely available); e.g.
echo $(seq 3) -> 1 2 3; start number 1 implied
echo $(seq -f '%02.f' 3) -> 01 02 03 - zero-padded
echo $(seq 2 4) -> 2 3 4; explicit start and end numbers
echo $(seq 1 2 5) -> 1 3 5; custom increment (the 2 in the middle)
If you DO need the numbers in the list to have a prefix or postfix, you have several choices:
Use the seq utility with its -f option for providing a printf-style format string (as used above for zero-padding), or pure Bash workarounds based on eval (extra care needed!) or building an array in a loop, all of which are detailed in this answer.
You could also consider implementing the functionality generically, such as by writing a custom shell function or a custom script with utilities such as awk or perl.
Example of safe use of eval with variables driving a sequence brace expression:
The variables are validated beforehand, to make sure they contain decimal integers.
from=1 to=3 # sample values
# Ensure that $from and $to are decimal numbers and abort, if they are not.
(( 10#$from + 10#$to || 1 )) 2>/dev/null || { echo "Need decimal integers" >&2; exit 1; }
eval echo "A{$from..$to}" # -> 'A1 A2 A3'
General overview of brace expansion
The main purpose of brace expansion is to expand to a list of tokens with each token having an optional prefix and/or postfix; brace expansions must be unquoted and come in 2 flavors:
a fixed series (list) of comma-separated strings - variables supported
specifies and expands to a fixed number of tokens (2 or more); e.g.:
echo A{b,c,d} -> Ab Ac Ad, i.e., 3 tokens, as implied by the number of args.
echo {/,$HOME/}Library e.g., -> /Library /User/jdoe/Library
Variable references - and even globs - are supported, but note that they get expanded after brace expansion, in its result, in the course of normal evaluation.
a sequence expression (range) with .., typically numerical - variables NOT supported
expands to a variable number of tokens, driven by literal start and end points (for historical reasons, use of variables is NOT supported - see the comments on user000001's answer):
[rare] strings: only single English letters allowed; e.g. {a..c}
numbers: decimal integers only; e.g., {1..10}, {10..1}, {-1..2}
example with prefix and postfix: A{1..3}# -> A1# A2# A3#
broken example with variables: {$from..$to} # !! FAILS - $from and $to are interpreted as literals and therefore not recognized as either a single letter or a decimal integer - no brace expansion is performed (see below).
by contrast, using variables does work in zsh and ksh.
bash 4+ adds two features:
optional increment step value:
echo A{1..5..2} -> A1 A3 A5 - numbers incremented by 2
ability to zero-pad:
echo A{001..003} -> A001 A002 A003
An invalid brace expression is not expanded (treated like a regular unquoted string, with { and } treated as literals):
echo {} -> '{}' - invalid as a brace expr.: at least 2 ,-separated tokens needed
This allows the use of unquoted {} with find, for instance.
echo {1..$to} -> '{1..<value-of-$to>}' - invalid as a brace expr. in bash: variables not supported; however, valid in ksh and zsh.
(fish, by contrast, expands any {...} sequence; similarly, zsh has option BRACE_CCL (OFF by default) for expanding individual characters inside {..}, which effectively causes expansion of any nonempty {...} sequence.)
The brace expansion is evaluated before the variables are expanded. You need a c-style for loop instead:
for ((i=1;i<=howmany;i++))
do
echo "Welcome"
done
create a sequence to control your loop
for i in $(seq 1 $howmany); do
echo "Welcome";
done
The problem is that the "brace expansion" is performed before the "variable expansion"
for i in $(seq 1 $howmany)
works as #damienfrancois said, or, if you would like:
for i in $(eval echo "{$start..10}")
probably does, but don't use it for everyone's sanity.
You could also use a while loop:
while ((howmany--)); do
echo "Welcome"
done
We could also use eval in this case:
howmany=`grep -c $1 /root/file`
for i in $(eval echo {1..$howmany}); do
echo "Welcome"
done

Why can I refer to a variable inside (( )) without the $ symbol?

In ABS guide, I could see below snippet
var1=5
var2=4
if (( var1 > var2 ))
then
echo "$var1 is greater than $var2"
fi
I am not able to understand, why we don't need $ symbol. I added $ symbol, shellcheck shows "$ symbol is not necessary on arithmetic variables".
I am still not able to understand how that dereferencing of var1 and var2 works...
Expressions inside ((...)) are evaluated in arithmetic context. Strings which can be variable names are considered as variables whose values are integers, since evaluating these strings as literal strings makes no sense in arithmetic context. These considerations are also valid for C style for loops: in for ((i = 0; i < 10; ++i)), preceding i with $ is not necessary (but it may be necessary, depending on the context, within the body of the loop).
(( ... )) follows the same evaluation rules as defined in the POSIX specification for arithmetic expressions. (The main difference is ((...)) produces an exit status reflecting if the result is zero/nonzero, while $((...)) produces the result as a string.) In particular:
If the shell variable x contains a value that forms a valid integer constant, optionally including a leading <plus-sign> or <hyphen-minus>, then the arithmetic expansions "$((x))" and "$(($x))" shall return the same value.
The shell variable var1 contains an integer constant, so $((var1)) and $(($var1)) are equivalent. This holds recursively as well.
Various shells seem to treat variables that refer to variables differently. Nothing in the POSIX wording seems to require the following kind of recursive evaluation, though both bash and dash do:
$ foo=bar
$ bar=5
$ echo $((foo)) # foo/$foo evaluates to bar, which contains an integer constant
5
bash seems to take it a step further, allowing any string to expand, followed by an attempt to evaluate the result as an arithmetic expression.
$ foo="x + 3"
$ x=5
$ echo $((foo)) # foo evaluates to x + 3, which evaluates to 5 + 3
8
but in dash:
$ foo="x+3"
$ x=5
$ echo $((foo))
dash: 3: Illegal number: x+3
It's documented in the manual in 6.5 Shell Arithmetic. It says
Within an expression, shell variables may also be referenced by name without using the parameter expansion syntax.
This features makes arithmetic expressions so much easier to read.
Interestingly, you can also refer to array elements like this as well
values=(42 54) i=0 j=1
echo $(( values[i] + values[j] )) # => 96
Other places you don't need $ (i.e. other arithmetic "contexts"):
For numerically-indexed arrays, the index (${values[i]} not ${values[$i]})
the offset and length parts of the ${var:offset:length} parameter expansion.
You don't need the $ if it's inside the (( )) for doing arithmetic.
I am still not able to understand how that dereferencing of var1 and var2 works
bash parses things differently inside the (( )), undoubtedly because it makes it much easier to read complex arithmetic expressions without the $.

Bash - how to evaluate curly brackets wildcard from a variable [duplicate]

This question already has answers here:
Brace expansion with variable? [duplicate]
(6 answers)
Closed 4 years ago.
Consider the following script:
#! /bin/bash -e
echo {foo,bar}
EX={foo,bar}
echo ${EX}
The output of this script is:
foo bar
{foo,bar}
I would like the the echo command to perform brace expansion on ${EX}. Thus, I would like to see an output of
foo bar
foo bar
I want to create a script where the user can supply a path with curly brackets where every expanded version of it is copied.
Something like this:
#! /bin/bash -e
$SOURCES=$1
$TARGET=$2
cp -r ${SOURCES} ${TARGET}
How can I achieve this?
This is a way:
ex=({foo,bar,baz})
echo ${ex[#]}
foo bar baz
See man bash:
The order of expansions is: brace expansion, tilde expansion, parameter, variable and arithmetic expansion and command substitution (done in a left-to-right fashion), word splitting, and pathname expansion.
As you see, variable expansion happens later than brace expansion.
Fortunately, you don't need it at all: let the user specify the braced paths, let the shell expand them. You can then just
mv "$#"
If you need to separate the arguments, use an array and parameter expansion:
sources=("${#:1:$#-1}")
target=${#: -1}
mv "${sources[#]}" "$target"
Brace expansion does not work the way you are attempting to use it. Brace expansion is basically used to generate lists to be applied within the context of the present command. You have two primary modes where brace expansion is used directly (and many more where brace expansion is used a part of another operator.) The two direct uses are to expand a list of items within a comma-separated pair of braces. e.g.
$ touch file_{a,b,c,d}.txt
After executing the command, brace expansion creates all four files with properly formatted file names in the present directory:
$ ls -1 file*.txt
file_a.txt
file_b.txt
file_c.txt
file_d.txt
You may also use brace-expansion in a similar manner to generate lists for loop iteration (or wherever a generated system/range of numbers in needed). The syntax for using brace expansion here is similar, but with .. delimiters within the braces (instead of ',' separation). The syntax is {begin..end..increment} (whereincrement can be both positive and negative) e.g.
$ for i in {20..-20..-4}; do echo $i; done)
20
16
12
8
4
0
-4
-8
-12
-16
-20
(note: using variables for begin, end or increment is not allowed without some horrible eval trickery -- avoid it.).

Use environment variable in word within command [duplicate]

This question already has answers here:
How do we separate variables from letters in shell scripting?
(5 answers)
What is the difference between ${var}, "$var", and "${var}" in the Bash shell?
(7 answers)
Closed 5 years ago.
I want to programmatically create this output:
s1a
s2a
s3a
s4a
s5a
where I may go to any number of rows, not just 5. My approach is to use a for loop, referencing an environment variable iterator:
$ for i in $(seq 1 5); do echo s$ia ; done
s
s
s
s
s
How can I differentiate between the environment variable $i and the character a? I want to avoided referencing some $ia variable.
Variable names are greedy, meaning they'll use as much of the text as possible to form a variable name. Hence s$ia is the literal s followed by the variable $ia.
You need to use braces to make it non-greedy:
echo s${i}a
This means the literal s the variable $i and the literal a:
pax> for i in $(seq 1 5) ; do echo s${i}a ; done
s1a
s2a
s3a
s4a
s5a
And keep in mind, modern bash versions can generate those sequences without resorting to the external seq:
for i in {1..5} ; do ...
There are no environment variables in your code, only shell variables.
Shell variables may also be environment variables (exported variables such as $PATH that are visible to all child processes, whether they're shells or not), but that's not the case here.
Shell variable names must be disambiguated from adjacent characters that could be part of a syntactically legal variable name by enclosing them in {...} - ${i}, in your case.
Unless you specifically want shell-variable references (possibly embedded in unquoted tokens) subjected to shell expansions, notably word-splitting and globbing, double-quote them - "s${i}a" in your case.
Use an arithmetic, C-style loop to create a memory-efficient loop with a variable number of iterations.
To put it all together:
$ n=5; for (( i = 1; i <= n; ++i )); do echo "s${i}a"; done
s1a
s2a
s3a
s4a
s5a
bash brace expansion can handle that without loops or variables
$ echo s{1..5}a | tr ' ' '\n'

Arithmetic expressions in Bash?

I had used several ways to do some simple integer arithmetic in BASH (3.2). But I can't figure out the best (preferred) way to do it.
result=`expr 1 + 2`
result=$(( 1 + 2 ))
let "result = 1 + 2"
What are the fundamental differences between those expressions?
Is there other ways to do the same?
Is the use of a tool like bc mandatory for floating point arithmetic?
result=`echo "7/354" | bc`
In Bash, let allows multiple assignments on a line:
let a=3 b=4 c=5
As you show in your question, quoting the argument to let allows you to put spaces around the operators. You can, however, omit the quotes if you avoid using spaces.
Another form using double parentheses at the beginning of the statement (instead of the i=$((j + 1)) form) allows you to include spaces around the equal sign or do post- or pre- increment or decrement and additional assignment operations:
(( a = ( b + c ) * 4 ))
(( count++ ))
(( d = --c**2 ))
(( e *= 2 ))
(( f = 3, g = 5 )) # multiple operations require a comma separator
If you do help "((" it says that the double parentheses is 'Equivalent to "let EXPRESSION".'
You can use the declare builtin to make assignments, including indirectly:
blue=2
name=blue
declare $name=4
echo $blue # result: 4
echo ${!name} # result: 4
Edit:
The $(()) construct is called "arithmetic expansion" and causes the contents to be evaluated as an integer expression. It's a syntax element of the shell.
If a variable is declared as an integer you don't need to use either form of double parentheses, you can omit the dollar sign from the variable name (as in the double-parentheses forms), but you can't add spaces around operators:
declare -i x=1 # set integer, initialize to 1
declare +i s=1 # clear integer, initialize to 1
x+=1 # could also be x=x+1
echo $x # result: 2 (addition)
s+=1 # could also be s=$s+1, requires a "$"
echo $s # result: 11 (string concatenation)
Unlike the forms above, calling expr involves spawning an external executable which can be quite expensive for a lot of calculations in a loop. The only time it should be used is in environments where the shell can't do its own arithmetic or for portability when a script may find its way into such an environment. POSIX shells have arithmetic capability so it would be a concern only with older systems.
Regarding the use of bc for floating point arithmetic, it or something similar is required when using Bash and many other shells. POSIX says that "Only signed long integer arithmetic is required."
Two shells that do support float math are ksh and zsh. In addition to bc, you can use dc, AWK, Python, Perl and others from within a Bash script.
One thing that Bash will do with floating point numbers is print them with the printf builtin (note that there is also an external printf, but builtins have priority).
printf "%'14.4f\n" 1234.56 # result " 1,234.5600" (in my locale)
I prefer your second option, since it doesn't need an external utility:
result=$(( 1 + 2 ))
The first option calls out to expr to do the math - I'm not familiar with let. Another alternative to bc is dc. Choose your favourite.
I can't say it's "mandatory" but bc is probably your best bet for general purpose arithmetic.
For something fancier, you can always pipe through Perl.
The downside of both thee approaches is that they both open a child process, so doing it in a tight loop will be slower than native bash expressions (same problem arises with use of backticks, in your first example). I'm not sure whether $(()) calls a child process.
Is the use of a tool like bc mandatory for floating point arithmetic?
No, if you are using a shell that supports floating point, eg zsh, ksh. Otherwise, if you want to do more advanced floating point maths, use either one of these, bc/awk/dc. Of course, Perl/Python etc as well.
The third option you have is a lot less readable as it does not look like an assignment operator. The first, as noted by others, calls an external command

Resources