reference a bash variable within a command using $() [duplicate] - bash

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.

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 encase a command line argument in quotes with a bash/shell script [duplicate]

This question already has answers here:
How do I pass in the asterisk character '*' in bash as arguments to my C program?
(5 answers)
The issue of * in Command line argument
(6 answers)
Closed 5 years ago.
How do I write a bash script/function that will take a command line argument with spaces and interpret it as if it had quotes around it.
ie: echo this is some text as if echo "this is some text"
What I want to do is create a simple CLI calculator script/function.
Here is what I have:
calc() {
echo $(($#))
}
On the CLI, all of these work:
"# +-/* ", #+-/*, # +-/ #
ie:
calc 10+2
12
but this one produces an error:
calc 10 * 2
-bash: 10 calc.sh 2: syntax error: invalid arithmetic operator (error token is ".sh 2")
Any ideas? It's not a big deal to include the quotes in the calculations, but if possible it would be quicker/more convenient to not include them. Is there any way to take a string after calc from first character to last and then pass it through quotes in the script?
In unix systems there is a variable called IFS. This variable decides at what signs the console separate the string into words. In theory you could chang the IFS variable, but this is really not advisable (as it will breake other bash commands).
Better: you could write your utillity in such way, that writing 'calc' will prompt the user to type his equation to standard in. Then you can read the text the user types and parse it in any way you want.
Even better: a shell script can specify that it wishes for an arbitrary nimber of words as input. Take all the words the user writes and then parse that. See: How to define a shell script with variable number of arguments?

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 Does Bash Tokenize Scripts?

Coming from a C++: it always seems like magic to me that some whitespace has an effect on the validity or semantics of the script. Here's an example:
echo a 2 > &1
bash: syntax error near unexpected token `&'
echo a 2 >&1
a 2
echo a 2>&1
a
echo a 2>& 1
a
Looking at this didn't help much. My main problem is that it does not feel consistent; and I am in a state of confusion.
I'm trying to find out how bash tokenizes its scripts. A general description thereof to clear up any confusion would be appreciated.
Edit:
I am NOT looking for redirections specifically. They just came up as example. Other examples:
A="something"
A = "something"
if [$x = $y];
if [ $x = $y ];
Why isn't there a space necessary between ] and ;? Why does assignment require an immediate equal sign? ...
2>&1 is a single operator token, so any whitespace that breaks it up will change the meaning of the command. It just happens to be a parameterized token, which means the shell will further tokenize it to determine what exactly the operator does. The general form is n>&m, where n is the file descriptor you are redirecting, and m is the descriptor you are copying to. In this case, you are saying that the standard error (2) of the command should be copied to whatever standard output (1) is currently open on.
The examples you gave have the behavior they do for good reason.
Redirection sources default to FD 1. Thus, >&1 is legitimate syntax on its own -- it redirects FD 1 to FD 1 -- meaning allowing whitespace before the > would result in an ambiguous syntax: The parser couldn't tell if the preceding token was its own word or a redirection source.
Nothing other than a FD number is valid under >&, unless you're in a very new bash which allows a variable to be dereferenced to retrieve a FD number. In any event, anything immediately following >& is known to be a file descriptor, so allowing optional whitespace creates no ambiguity there.
a = 1 is parsed as a legitimate command, not a syntax error: It runs the command a with the first argument = and the second argument 1. Disallowing whitespace within assignments eliminates this ambiguity. Similarly, a= foo has a separate and distinct meaning: It exports an environment variable a with an empty value while running the command foo. Relaxing the whitespace rules would disallow both of these legitimate commands.
[ is a command, not special syntax known to the parser; thus, [foo tries to find a command (named, say, /usr/bin/[foo), requiring whitespace.
; takes precedence in the parser as a statement separator, rather than being treated as part of a word, unless quoted or escaped. The same is true of & (another separator), or a newline.
The thing is, there's no single general rule which will explain all this; you need to read and learn the language syntax. Fortunately, there's not very much syntax: Almost all commands are "simple commands", which follow very simple and clear rules. You're asking about, and we're explaining, some of the exceptions to that; there are other exceptions, such as [[ ]] in bash, but they're small enough in total that they can be learned.
Other suggested resources:
http://aosabook.org/en/bash.html (The Architecture of Open Source Applications; chapter on bash)
http://mywiki.wooledge.org/BashParser (Wooledge wiki high-level description of the parser -- though this focuses more on expansion rules than tokenization)
http://mywiki.wooledge.org/BashGuide (an introductory guide to bash syntax in general, written with more of a focus on accuracy and best practices than some competing materials).

Resources