Expr and count in shell - shell

I learn a shell code from a video,and there is a code:
#!/bin/sh
i=1
while true
do
echo $i
i='expr $i + 1'
done
The code above gives me output is:
1
expr $i + 1
expr $i + 1
expr $i + 1
......
But in the video the output are consistent numbers,why the same code shows different results?

As aready mentioned in a comment, you have the wrong quotes; the video will have had backticks (`, ASCII 96), not single quotes (', ASCII 39).
Additionally, anything which uses this obsolescent syntax for command substitution or expr is at least 20 years behind, and should be avoided in favor of modern POSIX constructs.
#!/bin/sh
i=1
while true
do
echo "$i" # Note proper quoting
i=$((i+1))
done
In Bash, you could use brace expansion or a C-style for loop, but these are not portable to modern sh.
If you need your code to be portable to pre-POSIX systems, my recommendation would still be to learn the modern constructs first, especially for simple code like this (where in a production system you would probably want to use Awk or Perl for a loop like this anyway).

Related

Evaluate expression inside bash for loop [duplicate]

This question already has answers here:
How do I iterate over a range of numbers defined by variables in Bash?
(20 answers)
Closed 8 years ago.
If I do this, I get the result as expected.
for i in {125..129}; do echo $i; done
125
126
127
128
129
But when I do this? I get something weired.
for i in {$((1+(25-1)*500))..$((25*500))}; do echo $i; done
{12001..12500}
I wish to pass a variable inside the loop variables like $((1+($j-1)*500))
Bash's brace expansion has limitations. What you want is seq:
for i in $( seq $((1+(25-1)*500)) $((25*500)) ); do echo $i; done
The above will loop over all numbers from 12001 to 12500.
Discussion
seq is similar to bash's braces:
$ echo {2..4}
2 3 4
$ echo $(seq 2 4)
2 3 4
The key advantage of seq is that its arguments can include not just arithmetic expressions, as shown above, but also shell variables:
$ x=4; echo $(seq $((x-2)) $x)
2 3 4
By contrast, the brace notation will accept neither.
seq is a GNU utility and is available on all linux systems as well as recent versions of OSX. Older BSD systems can use a similar utility called jot.
Brace expansion is the very first expansion that occurs, before parameter, variable, and arithmetic expansion. Brace expansion with .. only occurs if the values before and after the .. are integers or single characters. Since the arithmetic expansion in your example has not yet occurred, they aren't single characters or integers, so no brace expansion occurs.
You can force reexpansion to occur after arithmetic expansion with eval:
for i in $(eval echo {$((1+(25-1)*500))..$((25*500))}); do echo $i;
You query is very similar to :
shell script "for" loop syntax
brace expansion, {x..y} is performed before other expansions, so you cannot use that for variable length sequences.
Instead try
for i in seq $((1+(25-1)*500)) $((25*500)); do echo $i; done
It is just echoing the text which is exactly as it says:
{12001..12500}
That is "{"+12001+"..."+12500+"}"
Don't do this with a for loop. The {..} notation is not that flexible:
i=$((1+(25-1)*500)); while test $i -le $((25*500)); do echo $((i++)); done
Try this
for (( i= $((1+(25-1)*500)); i<=$((25*500)); i++ )); do echo $i; done
or this
for i in $(seq $(( 1+(25-1)*500 )) $(( 25*500 )) ); do echo $i; done

Joining files with cat

i'm doing a script to download one file in multiple parts, i'm in the part of joining that files. I can join the parts outside of the script with
cat name.part{0..4} > name.ext
But, if i use this in the script
cat $filename.part{0..$N} > $filename
i get:
cat: Bitcoin-960x623.jpg.part{0..5}: No such file or
{0..4} is a brace expansion; $N is a variable expansion. Your expression doesn't work as you expect because brace expansion happens before variable expansion:
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 filename expansion.
http://www.gnu.org/software/bash/manual/bashref.html#Shell-Expansions
You can use the seq command instead of a brace expansion to generate the required filenames up to arbitrary N:
$ filename=myfile
$ N=4
$ cat $(seq -f "$filename.part%g" 0 $N) > name.ext
$
In the unlikely event seq -f is unavailable, you can use a for loop one-liner instead:
$ cat $(for ((i=0; i<N; i++)); do echo "$filename.part$i"; done) > name.ext
$
I solved with this.
for (( k=0 ; k<=$N ; k++ ))
do
cat $filename.part$k >> $filename
done
You cannot use variables inside curly braces in shell. eval can be used here:
eval "$filename.part{0..$N} > $filename"
Assertion: I'm going to argue that this answer is better. (Something I don't do often or lightly.) My rationale is that all of the other answers here at the time of this edit just expand to a range of integers, whether the files matching those integers exist or not. By using glob expansions you guarantee you will not encounter No such file or directory from cat. In all other cases with brace expansion, eval, seq and a for loop you'd have to explicitly check.
If there are fewer than 10 total filenames:
cat "$filename".part[0-9]
will expand as a glob to however many names there are. This is a fast and portable solution, but obviously limited. If there are more than 10 then that pattern will only match the ones with one digit, so you have to get a bit wilier and use extglob:
shopt -s extglob
cat "$filename".part+([0-9])
should do it, but this will only work for the Bourne-Again SHell. (That said, several other modern shells have expansion capabilities similar to extglob.)

Shell scripting - which word is first alphabetically?

How can you check which words is first alphabetically between two words?
For example in the code
#!/bin/bash
var1="apple"
var2="bye"
if [ $var1 \> $var2 ]
then
echo $var1
else
echo $var2
fi
I want it to print apple, since apple comes before bye alphabetically, but it isnt working as intended. What am I doing wrong?
What you need to do to solve the immediate problem is reverse the sense of your statement, since the "less than" operator is < rather than >.
Doing so will get it working correctly:
if [ $var1 \< $var2 ]
Alternatively, you can use the [[ variant which doesn't require the escaping:
if [[ $var1 < $var2 ]]
I prefer the latter because:
it looks nicer; and
the [[ variant is much more expressive and powerful.
You'll want to use the [[ ]] construct and print out the one that is less than the other
#!/bin/bash
var1="apple"
var2="bye"
if [[ $var1 < $var2 ]]; then
echo $var1
else
echo $var2
fi
It seems that you are suffering from two misconceptions about (bash) shell scripting.
First the line if [ $var1 > $var2 ] then echo ... is syntactically wrong and you should really paste your example commands or code rather than trying to re-type them from memory. It would be fine if you'd said if [[ "$var1" > "$var2" ]]; then ... or if [ "$var" \> "$var2" ]; then ....
Note that [[ is a bash specific conditional expression while [ (single bracket) introduces the shell built-in implementation of the /usr/bin/[ (alias for /usr/bin/test) command.
The old [ (test) command has much more constrained features than the [[ support in bash. It only support -lt, -eq ... and other integer comparisons and the various file and value length (-z' and-n) and other tests. It has no support for lexical/string or pattern (regex nor glob) comparisons. Thebashbuilt-in for[supports a number of the[[` extensions but, as shown some of them have to be explicitly escaped from the legacy parsing.
Also note that it's dangerous to use bar dereferences of $var (vs "$var" with the quotes). If the value assigned to var has any embedded spaces or various other operators which might be conflated with the switches to the test command.
Also you need the ; to separate the if command from the then clause.
Trying to write shell scripts as though shell were a normal programming language will lead you to quite a bit of this sort of confusion. Shells, such as bash have relatively little syntax and built-in functionality and most of that is glue around running commands. In early versions of UNIX the shell didn't have any built-in tests and relied entirely on the external test command. Over time more and more functionality was built-in the shell, often through aliases to those old commands (/usr/bin/[' is literally a link to the/usr/bin/testcommand and the shell built-ins for[andtest` are internal aliases to one another and implemented as (mostly?) compatible with the older (and still extant) external binaries.
Similarly all arithmetic operations in the early Bourne shells were done using external commands such as /usr/bin/expr. Korn shell and Bash added $((...)) and let and ((...)) expressions
for evaluating arithmetic expressions from within the shell without external command support.
Other examples relate to the support for arrays (declare) and parameter expansion ${var#...}
various other forms.
It's generally best to avoid most such features, or use them sparingly, as the resulting scripts because progressively less portable as you use them ... and the syntactic machinations rapidly overwhelm the code. At some point it's best to use Perl, Python, Ruby or some general purpose programming/scripting language for performing the general programming work and use shell for the purposes to which it was designed ... as glue around external commands, for marshaling data and variables into and out of those external commands/processes.

Handling arithmetic expressions in shell scripting

Kindly tell me that is it necessary to use "expr" keyword.
EG:-
echo `expr a*b`
And where we can simply handle arithmetic expressions using simple arithmetic operators.
EG:-
echo a*b
Thanks in advance.
In a Posix shell you can evaluate expressions directly in the shell when they are enclosed in
$(( ... ))
So:
a=12
b=34
echo $(($a + $b))
And although this wasn't always the case, all Posix shells you are likely to encounter will also deal with:
echo $((a + b))
This all happened because, a long time ago, the shell did not do arithmetic, and so the external program expr was written. These days, expr is usually a builtin (in addition to still being in /bin) and there is the Posix $((...)) syntax available. If $((...)) had been around from day one there would be no expr.
The shell is not exactly a normal computer language, and not exactly a macro processor: it's a CLI. It doesn't do inline expressions; an unquoted * is a wildcard for filename matching, because a CLI needs to reference files more often than it needs to do arithmetic.
The second form will almost surely never do what you want. In Bash, you have a built-in numeric expression handler, though:
A=4; B=6; echo $((A * B))
You can do arithmatic using $(())
echo $((2*3))
results in 6

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