Order of arithmetic expansion - bash

Consider the following:
for i in 1 2 3; do echo $(( j += 1 ))& done
According to (my reading of) the sh language spec, section 2.3 paragraph 5, the arithmetic expansion of j += 1 should take place during token recognition, and should thus be processed before the shell ever reads the &. So it seems that executing the above line should increment j by 3 and each invocation of echo should get a different argument. (Which is the behavior if '&' is replaced by ';'). In bash 3.2.25, j is not modified. Is this a bug in bash, or am I misunderstanding something?

That entire token recognition section deals with parsing, not expansion or command evaluation of any kind. Have a look at the "introduction" for context - parsing comes before basically everything, while evaluation of the arithmetic expression is usually one of the very last evaluation steps. It isn't really relevant here.
You're probably actually wondering about whether expansions occur before the subshell forks for asynchronous lists. They don't.
$ ksh93 -c 'typeset -i n; while ((++j%10)); do { n+=1; printf "$n "; } & done; while ((++j%10)); do : $((n++)) ${ printf "$n " >&2;} & done 2>&1; echo'
1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
$
$ bash -c 'f() { printf "#%d %s, %s\n" "$#"; }; f 1 $BASHPID $$; f 2 $BASHPID $$ & sleep 1'
#1 12275, 12275
#2 12276, 12275
$
As for the order of expansions, arithmetic expansion is performed at the same time as parameter expansion and command substitution, from left-to-right (also as shown above in the second loop).
I think you're not the only one to misread that section, I've filed a number of bugs related to it which have turned out to be either an error on my part or due to some unfortunate interaction with non-POSIX shell extensions. IMHO that section is too terse.
If you think there's a problem with the spec language or the implementations you'll likely find better answers on one of the mailing lists. ast-users, help-bash, austin group lists

Related

Unix script error while assigning command substitution to array variable [duplicate]

This question already has answers here:
Why does my Bash code fail when I run it with 'sh'?
(2 answers)
Closed 2 years ago.
From the below script I am assigning the command substitution for the array variables.
When I execute the script, the script is throwing error as
myscript.sh: 22: scr4.sh: Syntax error: "(" unexpected
ie while executing the command suite=( echo $Suites ).
I took this script from Advanced Bash scripting Guide pdf.
When I run this command by command in CLI, there is no issue.
What is the solution for this?
#!/bin/bash
Suites="Clubs
Diamonds
Hearts
Spades"
Denominations="2
3
4
5
6
7
8
9
10
Jack
Queen
King
Ace"
suite=( `echo $Suites` )
denomination=( `echo $Denominations` )
num_suites=${#suite[*]}
num_denominations=${#denomination[*]}
echo -n "${denomination[$((RANDOM%num_denominations))]} of "
echo ${suite[$((RANDOM%num_suites))]}
exit 0
You're probably using the wrong interpreter.
$ bash cards.sh
King of Diamonds
$ sh cards.sh
cards.sh: 22: Syntax error: "(" unexpected
POSIX shell does not support arrays. You define an array on line 22 (suite=( `echo $Suites` )) and another on the next code line, then most of your remaining lines refer to those arrays. $RANDOM is also unavailable in POSIX.
Another note, you don't need those subshells. Here's a cleaned up version of your code:
#!/bin/bash
suit=( Clubs Diamonds Hearts Spades )
denomination=( {2..10} Jack Queen King Ace )
echo -n "${denomination[ $(( RANDOM % ${#denomination[#]} )) ]} of "
echo "${suit[ $(( RANDOM % ${#suit[#]} )) ]}"
exit 0
This doesn't launch any subshells. Instead, it's directly defining the arrays. I used {2..10} (another bashism) as shorthand for listing each number. Rather than storing the lengths in variables, I'm invoking them directly in the modulo math, which I've spaced out to make more legible.
I changed ${variable[*]} to ${variable[#]} as well. This doesn't matter in your particular code, but it's generally better to use # since it is better at preserving spacing.
I also corrected the spelling of your suite variable to suit. Feel free to revert if that's actually what you desired (perhaps due to a spoken language difference). It of course doesn't really matter as long as you're consistent; bash doesn't know English 😉

What does while(($#)); do ...; shift; done mean in bash, and why would someone use it?

I came across the following while loop in a bash script tutorial online:
while(($#)) ; do
#...
shift
done
I don't understand the use of the positional parameter cardinality in the while loop. I know what the shift command does, but does the while statement have some special use in conjunction with shift?
Every time you do shift, the number of positional parameters is reduced by one:
$ set -- 1 2 3
$ echo $#
3
$ shift
$ echo $#
2
So this loop is executed until every positional parameter has been processed; (($#)) is true if there is at least one positional parameter.
A use case for doing this is (complex) option parsing where you might have options with an argument (think command -f filename): the argument to the option would be processed and removed with an additional shift.
For examples of complex option parsing, see BashFAQ/035 and ComplexOptionParsing. The last example of the second link, Rearranging arguments, uses the exact while (($#)) technique.
Let's have a script shi.sh:
while(($#)) ; do
echo "The 1st arg is: ==$1=="
shift
done
run it with:
bash shi.sh 1 2 3 #or chmod 755 shi.sh ; ./shi.sh 1 2 3
you will get
The 1st arg is: ==1==
The 1st arg is: ==2==
The 1st arg is: ==3==
Note the: 1st (and the usage of $1).

Reading Column and Find Median (Bash)

I want to find the median for each column, however it doesn't work like what I want.
1 2 3
3 2 1
2 1 5
I'm expecting for
2 2 3
for the result, however turns out it just give sum error and some "sum" of the column. Below is a snippet of the code for "median in column"
while read -r line; do
read -a array <<< "$line"
for i in "${!array[#]}"
do
column[${i}]=${array[$i]}
((length[${i}]++))
result=${column[*]} | sort -n
done < file
for i in ${!column[#]}
do
#some median calculation.....
Notes: I want to practice bash, that's why I hard-coded using bash.
I really appreciate if someone could help me, especially in BASH. Thank you.
Bash is really not suitable for low-level text processing like this: the read command does a system call for each character that it reads, which means that it's slow, and it's a CPU hog. It's ok for processing interactive input, but using it for general text processing is madness. It would be much better to use awk (Python, Perl, etc) for this.
As an exercise in learning about Bash I guess it's ok, but please try to avoid using read for bulk text processing in real programs. For further information, please see Why is using a shell loop to process text considered bad practice? on the Unix & Linux Stack Exchange site, especially the answer written by
Stéphane Chazelas (the discoverer of the Shellshock Bash bug).
Anyway, to get back to your question... :)
Most of your code is ok, but
result=${column[*]} | sort -n
doesn't do what you want it to.
Here's one way to get the column medians in pure Bash:
#!/usr/bin/env bash
# Find medians of columns of numeric data
# See http://stackoverflow.com/q/33095764/4014959
# Written by PM 2Ring 2015.10.13
fname=$1
echo "input data:"
cat "$fname"
echo
#Read rows, saving into columns
numrows=1
while read -r -a array; do
((numrows++))
for i in "${!array[#]}"; do
#Separate column items with a newline
column[i]+="${array[i]}"$'\n'
done
done < "$fname"
#Calculate line number of middle value; which must be 1-based to use as `head`
#argument, and must compensate for extra newline added by 'here' string, `<<<`
midrow=$((1+numrows/2))
echo "midrow: $midrow"
#Get median of each column
result=''
for i in "${!column[#]}"; do
median=$(sort -n <<<"${column[i]}" | head -n "$midrow" | tail -n 1)
result+="$median "
done
echo "result: $result"
output
input data:
1 2 3
3 2 1
2 1 5
midrow: 3
result: 2 2 3

Double parentheses to increment a variable in bash

Suppose I increment a variable in bash. For instance,
> i=0; for f in `ls *.JPG`; do echo $f $i; ((i++)); done
a0.jpg 0
a1.jpg 1
...
Now I wonder why I need those double parentheses to increment i.
The double parentheses construct is a shell feature to support arithmetic operations.
The same construct can also be used for Loops and special numerical constants.
Also, copied from the first link :
# -----------------
# Easter Egg alert!
# -----------------
# Chet Ramey seems to have snuck a bunch of undocumented C-style
#+ constructs into Bash (actually adapted from ksh, pretty much).
# In the Bash docs, Ramey calls (( ... )) shell arithmetic,
#+ but it goes far beyond that.
# Sorry, Chet, the secret is out.
i++ is a perfectly valid file name, and if I have access to your system, I can make that into a command that does something you don't want.
Try creating a file, /bin/i++ with this content:
#!/bin/sh
echo 'Gotcha'
and then chmod +x /bin/i++

Shell Script - Pair numbers with a for statement

I'm trying to make a script that adds pairs of number, using only simple for/if statements, for example:
./pair 1 2 4 8 16
would result in
./pair 1 2 4 8 16 32
3
12
48
My current script is (probably hugely wrong, I'm new to Shell Scripting):
#!/bin/sh
sum=0
count=0
for a in $*
do
sum=`expr $sum + $a`
count=`expr $count + 1`
if [ count=2 ]
then
sum=0
count=0
fi
echo "$sum"
done
exit
However this is not working. Any help would be great.
A for loop isn't really the right tool for this. Use a while loop and the shift command.
while [ $# -gt 1 ]; do
echo $(( $1 + $2 ))
shift 2
done
The problem in your script is that you do not have sufficient whitespace in your if statement, as well as not preceding the variable name with a $:
if [ $count = 2 ]
Also, you only want to output $sum just before you reset its value to 0, not every time through the loop.
And, expr isn't needed for arithmetic anymore: sum=$(( sum + a ))
My approach uses cshell. As a C programmer, I connect better with cshell. But that asside, I approach the problem with an iterator, incrementing twice.
macetw
I haven't seen anyone write in Csh for a long time. Back in the early Unix days, there were two primary shells: Csh and Bourne shell. Bourne was the AT&T flavor and BSD (Berkeley Standard Distribution) used Csh which took syntax hints from the C language.
Most people I knew used Csh as their standard shell, but wrote scripts in Bourne shell because Csh had all sorts of issues as pointed out by Tom Christiansen (as already mentioned by Olivier Dulac). However, Csh had command aliases and command line history and editing. Features that people wanted in their command line shell.
Csh peaked back in the SunOS days. SunOS was based upon BSD and Sun administrators wrote almost all of their scripts in Csh. However, David Korn changed a lot of that with Kornshell. Kornshell contained the standard Bournshell syntax language, but contained features like command aliasing and shell history editing that people wanted in Csh. Not only that, but it contained a lot of new features that were never previously found in shells like built in math (goodbye to expr and bc), pattern matching with [[ ... ]] and shell options that could be set with set -o.
Now, there was little need to know Csh at all. You could use Kornshell as both your scripting language and your command line shell. When Sun replaced SunOS with Solaris, the Kornshell and not the C shell was the default shell. That was the end of C shell as a viable shell choice.
The last big advocate of Csh was Steve Jobs. NeXT used the TurboCsh as its default shell, and it was the default shell on the first iteration of Mac OS X -- long after Sun had abandoned it. However, later versions of Mac OS X defaulted to BASH.
BASH is now the default and standard. As I mentioned before, on most systems, /bin/sh is BASH. In the BASH manpage is this:
If bash is invoked with the name sh, it tries to mimic the startup behavior of historical versions of sh as closely as possible, while conforming to the POSIX standard as well.
That means that solid BASHisms (shopt, ${foo/re/repl}, etc.) are not active, but most of the stuff inherited from Kornshell ([[ ... ]], set -o, typeset, $(( ... )) ) are still available. And, that means about 99% of the BASH scripts will still work even if invoked as /bin/sh.
I believe that most of the criticisms in Tom Christiansen anti-Csh paper no longer apply. Turbo C shell - which long ago replaced the original C shell fixed many of the bugs. However, Turbo C came after much of the world abandoned C shell, so I really can't say.
The Csh approach is interesting, but is not a correct answer to this question. C Shell scripting is a completely different language than Bourne shell. You'd be better off giving an answer in Python.
My approach uses cshell. As a C programmer, I connect better with cshell. But that asside, I approach the problem with an iterator, incrementing twice.
# i = 1
while ( $i < $#argv )
# j = $i + 1
# sum = $argv[$i] + $argv[$j]
echo $sum
# i = $i + 2
end
So the shebangs inplies bourne shell, and if you start the script directly it will be the same as doing:
/bin/sh <your_script_name>
And the simplest loop I can think of would be this one:
while [ $# -gt 1 ]; do
expr $1 + $2
shift 2
done
It counts the input tokens and as long as you have more than 2 (the square brackets test) it adds the first and the second one displaying the result. shift 2 means shifting the input two places (i.e. $1 and $2 are dropped and $n will be mapped to $n-2).
If you want a funny sh/bc combo solution:
#!/bin/sh
printf "%s+%s\n" "$#" | bc
It's cool, printf takes care of the loop itself. :)
Of course, there are no error checkings whatsoever! it will work with floats too.
The same with dc:
#!/bin/sh
printf "%s %s+pc" "$#" | dc
but it won't work with negative numbers. For negative numbers, you could:
#!/bin/sh
printf "%s %s+pc" "${#/-/_}" | dc
And I just realized this is the shortest answer!

Resources