Bash operator ++ behavior weired with a constant - bash

By occasionally, I meet a problem when calculating a self-increment operation ++ with a constant, the behavior is weird for me (this is not the original code, I just copied the looks-like lines).
#!/bin/bash
echo "out1="$((++5))
echo "out2="$((5++))
The code snippet looks like about; when I execute it, I got following result:
$ bash test.sh
out1=5
test.sh: line 3: 5++: syntax error: operand expected (error token is "+")
The bash version is:
GNU bash, version 4.2.46(2)-release (x86_64-redhat-linux-gnu)
I want to know:
why there is no syntax error for pre-increment operator ?
why the post-increment operate return a value 5, other than 6.
Thanks.

I can't say for sure, I don't feel like browsing the source to learn about the arithmetic expression parser. My guess is:
pre-increment is not actually preincrement, but that you can put any number of + and - symbols before a constant to determine if it's positive or negative. I suspect the parser is seeing $(( +(+5) ))
summary: you're just specifying the number's sign.
as post-incrementing a constant makes no sense (you can't assign the constant 5 with the value 6), it is taken as if you were typing "5 plus ... something" and instead of an arithmetic value you gave another plus sign. So, syntax error, and "operand error" message. Like the parser is seeing $(( (5) + (+) ))
summary: the first plus is OK, the second is an error.

Related

Extracting git commit information in GitHub action workflow- use of '$' symbol [duplicate]

This question already has answers here:
Backticks vs braces in Bash
(3 answers)
Brackets ${}, $(), $[] difference and usage in bash
(1 answer)
Closed 4 years ago.
I have two questions and could use some help understanding them.
What is the difference between ${} and $()? I understand that ()
means running command in separate shell and placing $ means passing
the value to variable. Can someone help me in understanding
this? Please correct me if I am wrong.
If we can use for ((i=0;i<10;i++)); do echo $i; done and it works fine then why can't I use it as while ((i=0;i<10;i++)); do echo $i; done? What is the difference in execution cycle for both?
The syntax is token-level, so the meaning of the dollar sign depends on the token it's in. The expression $(command) is a modern synonym for `command` which stands for command substitution; it means run command and put its output here. So
echo "Today is $(date). A fine day."
will run the date command and include its output in the argument to echo. The parentheses are unrelated to the syntax for running a command in a subshell, although they have something in common (the command substitution also runs in a separate subshell).
By contrast, ${variable} is just a disambiguation mechanism, so you can say ${var}text when you mean the contents of the variable var, followed by text (as opposed to $vartext which means the contents of the variable vartext).
The while loop expects a single argument which should evaluate to true or false (or actually multiple, where the last one's truth value is examined -- thanks Jonathan Leffler for pointing this out); when it's false, the loop is no longer executed. The for loop iterates over a list of items and binds each to a loop variable in turn; the syntax you refer to is one (rather generalized) way to express a loop over a range of arithmetic values.
A for loop like that can be rephrased as a while loop. The expression
for ((init; check; step)); do
body
done
is equivalent to
init
while check; do
body
step
done
It makes sense to keep all the loop control in one place for legibility; but as you can see when it's expressed like this, the for loop does quite a bit more than the while loop.
Of course, this syntax is Bash-specific; classic Bourne shell only has
for variable in token1 token2 ...; do
(Somewhat more elegantly, you could avoid the echo in the first example as long as you are sure that your argument string doesn't contain any % format codes:
date +'Today is %c. A fine day.'
Avoiding a process where you can is an important consideration, even though it doesn't make a lot of difference in this isolated example.)
$() means: "first evaluate this, and then evaluate the rest of the line".
Ex :
echo $(pwd)/myFile.txt
will be interpreted as
echo /my/path/myFile.txt
On the other hand ${} expands a variable.
Ex:
MY_VAR=toto
echo ${MY_VAR}/myFile.txt
will be interpreted as
echo toto/myFile.txt
Why can't I use it as bash$ while ((i=0;i<10;i++)); do echo $i; done
I'm afraid the answer is just that the bash syntax for while just isn't the same as the syntax for for.
your understanding is right. For detailed info on {} see bash ref - parameter expansion
'for' and 'while' have different syntax and offer different styles of programmer control for an iteration. Most non-asm languages offer a similar syntax.
With while, you would probably write i=0; while [ $i -lt 10 ]; do echo $i; i=$(( i + 1 )); done in essence manage everything about the iteration yourself

How to read argument value inside for loop range for shell scripting [duplicate]

I'm working on getting accustomed to shell scripting and ran across a behavior I found interesting and unexplained. In the following code the first for loop will execute correctly but the second will not.
declare letters=(a b c d e f g)
for i in {0..7}; do
echo ${letters[i]}
done
for i in {0..${#letters[*]}}; do
echo ${letters[i]}
done
The second for loop results in the following error:
syntax error: operand expected (error token is "{0..7}")
What confuses me is that ${#letters[*]} is clearly getting evaluated, correctly, to the number 7. But despite this the code fails even though we just saw that the same loop with {0..7} works perfectly fine.
What is the reason for this?
I am running OS X 10.12.2, GNU bash version 3.2.57.
The bracket expansion happens before parameter expansion (see EXPANSIONS in man bash), therefore it works for literals only. In other words, you can't use brace expansion with variables.
You can use a C-style loop:
for ((i=0; i<${#letters[#]}; i++)) ; do
echo ${letters[i]}
done
or an external command like seq:
for i in $(seq 1 ${#letters[#]}) ; do
echo ${letters[i-1]}
done
But you usually don't need the indices, instead one loops over the elements themselves, see #TomFenech's answer below. He also shows another way of getting the list of indices.
Note that it should be {0..6}, not 7.
Brace expansion occurs before parameter expansion, so you can't use a variable as part of a range.
Expand the array into a list of values:
for letter in "${letters[#]}"; do
echo "$letter"
done
Or, expand the indices of the array into a list:
for i in ${!letters[#]}; do
echo "${letters[i]}"
done
As mentioned in the comments (thanks), these two approaches also accommodate sparse arrays; you can't always assume that an array defines a value for every index between 0 and ${#letters[#]}.

Why does a space in a variable assignment give an error in Bash? [duplicate]

This question already has answers here:
Assignment of variables with space after the (=) sign?
(4 answers)
Closed 3 years ago.
#!/bin/bash
declare -r NUM1=5
NUM2 =4 # Line 4
num3=$((NUM1 + NUM2))
num4=$((NUM1 - NUM2))
num5=$((NUM1 * NUM2))
num6=$((NUM1 / NUM2)) # Line 9
echo "$num3"
echo $((5**2))
echo $((5%4))
I am using this bash script, and when I was running the script, I got the error
./bash_help
./bash_help: line 4: NUM2: command not found
./bash_help: line 9: NUM1 / NUM2: division by 0 (error token is "NUM2")
5
25
1
So I have changed the code to this and the error was gone.
#!/bin/bash
declare -r NUM1=5
NUM2=4
num3=$((NUM1 + NUM2))
num4=$((NUM1 - NUM2))
num5=$((NUM1 * NUM2))
num6=$((NUM1 / NUM2))
echo "$num3"
echo $((5**2))
echo $((5%4))
Why can't we use spaces when we assign a value to a variable? It is convention to use spaces for better readability of the code. Can anyone explain this?
It's not a convention in bash (or, more generally, POSIX-family shells).
As for "why", that's because the various ways of doing it wrong all have valid meanings as commands. If you made NUM2 = 4 an assignment, then you couldn't pass = as a literal argument without quoting it. Consequently, any such change would be backwards-incompatible, rather than being placed in undefined space (where extensions to the POSIX sh standard need to live to avoid constituting violations of that standard).
NUM2= 4 # runs "4" as a command, with the environment variable NUM2 set to an empty string
NUM2 =4 # runs "NUM2" as a command, with "=4" as its argument
NUM2 = 4 # runs "NUM2" as a command, with "=" as its first argument, and "4" as another
In Bash, functions are passed arguments as whitespace separated words.
From the documentation
"Each operator and operand must be a separate argument."
Variable assignment is different and uses this syntax name=[value]
The reason you can't put unquoted spaces around the equals sign is because bash would interpret this as a command.
The reason is, quite simply, that the shell is built to behave like this. It may not make sense for someone with experience in other programming languages (if you call shell syntax a "language", which in a sense it is).
Shell scripting makes it possible in many cases to simply not quote strings (as long as a sequence of characters meant to be a single string does not contain any spacing or special characters). Thanks to this, you can write :
my_command -n -X arg1 arg2
Instead of (in some kind of imaginary pseudo code)
"my_command" "-n" "-X" "arg1" "arg2"
In most languages, it is the other way around : literal strings are quoted, which frees "syntax space" for using variables without any special character (like $ in shell scripting).
Shell syntax provides convenience in frequent cases, at the cost of, well, less convenience (and readability) when doing some other things. It is both a curse and a blessing. The nice thing is knowing that if you have an interactive shell, you can be 100% sure you have an interpreter that will handle some kind of (maybe inelegant) programs. Due to its universal availability (despite various flavors being in existence), the shell is a kind of platform that is useful enough to be worth learning.

Does "untyped" mean the same as "dynamically typing"? [duplicate]

This question already has answers here:
Does "untyped" also mean "dynamically typed" in the academic CS world?
(9 answers)
Closed 6 years ago.
According to Advanced Bash-Scripting Guide,
bash variables are untyped:
Unlike many other programming languages, Bash does not segregate its variables by "type." Essentially, Bash variables are character
strings, but, depending on context, Bash permits arithmetic
operations and comparisons on variables. The determining factor is
whether the value of a variable contains only digits.
The link also gives examples.
Does "untyped" mean the same as the concept of "dynamically typing"
in programming languages? If not, what are the relations and
differences between the two?
To lighten the burden of keeping track of variable types in a script, Bash does permit declaring variables.
For example, declare a variable to be integer type, by declare -i
myvariable.
Is this called "typed" variables? Does "typed" mean
the same as the concept of "statically typing"?
Most of this has been well answered here...
Does "untyped" also mean "dynamically typed" in the academic CS world?
by at least two people that are very familiar with the matter. To most of us that have not studied type systems etc to that level 'untyped' means dynamic typing but it's a misnomer in academic circles, see post above. untyped actually means there are no types ie think assembly, Bash is typed, it figures out it's types at runtime. Lets take the following sentence from the Advanced Bash Scripting Guide, emphasis mine...
http://tldp.org/LDP/abs/html/untyped.html
Unlike many other programming languages, Bash does not segregate its
variables by "type." Essentially, Bash variables are character
strings, but, depending on context, Bash permits arithmetic operations
and comparisons on variables. The determining factor is whether the
value of a variable contains only digits.
Bash figures out that something is a number at runtime ie it's dynamically typed.
In assembler on a 64bit machine I can store any 8 bytes in a register and decrement it, it doesn't check to see if the things were chars etc, there is no context about the thing it's about to decrement it just decrements the 64 bits, it doesn't check or work out anything about the type of the thing it's decrementing.
Perl is not an untyped language but the following code might make it seem like it treats everything as integers ie
#!/usr/bin/perl
use strict;
use warnings;
my $foo = "1";
my $bar = $foo + 1;
print("$bar\n");
$foo was assigned a string but was incremented? Does this means Perl is untyped because based on context it does what you want it to do? I don't think so.
This differs from Python, Python will actually give you the following error if you try the same thing...
Traceback (most recent call last):
File "py.py", line 2, in <module>
bar = foo + 1
If Python is dynamically typed and Perl is dynamically typed why do we see different behavior. Is it because their type systems differ or their type conversion semantics differ. In assembly do we have type conversion instructions that change a string to an integer or vice versa?
Bash has different type conversion rules
#!/bin/bash
set -e
MYVAR=WTF
let "MYVAR+=1"
echo "MYVAR == $MYVAR";
This will assign 1 to MYVAR instead of incrementing it ie if you increment a string bash sets the string to integer zero then does the increment. It's performing type conversion which means it's typed.
For anyone still believing that Bash is untyped try this....
#!/bin/bash
declare -i var1=1
var1=2367.1
You should get something like this...
foo.sh: line 3: 2367.1: syntax error: invalid arithmetic operator (error token is ".1")
But the following shows no such error
#!/bin/bash
var1=2367.1
The output of the following
#!/bin/bash
var1=2367.1
echo "$var1"
let "var1+=1"
echo "$var1"
is the same warning without declaring a type...
2367.1
foo.sh: line 4: let: 2367.1: syntax error: invalid arithmetic operator (error token is ".1")
2367.1
A much better example is this
#!/bin/bash
arg1=1234
arg2=abc
if [ $arg1 -eq $arg2 ]; then
echo "wtf";
fi
Why do I get this...
foo.sh: line 5: [: abc: integer expression expected
Bash is asking me for an integer expression.
Bash is a dynamically typed or more correctly it's a dynamically checked language. I've already added a long answer, this is the short one.
#!/bin/bash
arg1=1234
arg2=abc
if [ $arg1 -eq $arg2 ]; then
echo "wtf";
fi
gives this error message....
foo.sh: line 5: [: abc: integer expression expected
The fact I have an error that tells me I have in some way made a mistake with regards type means something is checking types.

How to count number of lines in file and then use maths on that variable in bash?

I am currently using a bash script to pipe a few other codes together but am new and have been stuck on this for the last day or so. Basically I need to count the number of lines within a file and then divide that by 4 to get the true number of objects in that file (each object takes up 4 lines).
For this I have looked around and ended up with the following code:
a=$(wc -l "${o}"*)
k=$(wc -l Unmatched_forward.fq)
x=4
#declare -i $a
declare -i $k
stats1_2=$((a / x))
stats2_2=$((k / x))
echo "${stats1_2} reads were joined."
echo "${stats2_2} reads were not joined."
Within this code ${o} is the output from a previous file however needs to have ".fq" added to the end but whenever I try to add that to the end it comes up the error message below I have been trying to use the "*" to run on the file of which there are no other files similar.
"Unmatched_forward.fq" is another output file which I want to count the number of objects in.
I am using the declare option because I read that otherwise the number will be in string form instead of an integer and so maths cannot be done.
If anyone can help and explain whats wrong that would be great.
The error message is:
Overlay_code.sh: line 638: declare: `1265272': not a valid identifier
Overlay_code.sh: line 638: declare: `Unmatched_forward.fq': not a valid identifier
Overlay_code.sh: line 643: 1265272 Unmatched_forward.fq: syntax error: invalid arithmetic operator (error token is ".fq")
Whats more confusing is I am suddenly getting the '1265272' number appearing and have no idea why!
You should check that your invocation of wc truly returns only an integer, because I think it is not. Probably the following happens
$> wc -l Unmatched_forward.fq
128 Unmatched_forward.fq
So it returns the line count and the filename.
The following should work
k=$(wc -l Unmatched_forward.fq | awk '{print $1}')
x=4
stats1_2=$((k / x))
Note that bash's (()) only supports integer math, so all results will get rounded. If you need floating point precision, check out bc
You mean declare -i k. When you include the $, you're causing the variable name to be replaced with its value. But you want to say that the variable k is integer-typed.

Resources