This is normally found at the start of bash files.: #/bin/bash. The ‘shebang’: #! Must be followed with the path the the interpreter. It is a convention in Linux shells. PowerShell seems to figure it out by the extension though. Then you can just execute the script by just writing the file path, rather than bash path

To assign a value to a variable, the variable name is followed by the assignment operator (=) and the value. For example, VARIABLE=value. Cannot use spaces in variable assignment. Must use local VARIABLE=value. before any variable assignment to enforce local variables.

To access the value stored in a variable, the variable name is preceded by a dollar sign ($). For example, echo $VARIABLE

To use the value of a variable in a string, the variable name is enclosed by curly braces {} and preceded by a dollar sign. For example, echo “The value of the variable is ${VARIABLE}"

In bash, $(command) is used to execute the command inside the parenthesis and substitute the output of that command in place of the expression. It is known as command substitution. For example, echo Today is $(date) will execute the date command and substitute the output in place of $(date) so the output will be like “Today is Mon Jan 16 15:12:43 PST 2022”

To use or set an environment variable in a script, you should prefix the variable with export keyword. export MY_ENV_VAR="Hello World". Now in the shell, this variable will be available. Environment variables are only available for the current session

You can use a here-doc to include the contents of another file in a bash script. You can use cat command and the here-doc notation to include the contents of a file in your script, like this:

cat << EOF
$(cat /path/to/file)
EOF

Gets the first match of a file glob:

Echo $(ls -1 .txt)

.profile and .bashrc

These scripts run every time you log in and are used to export variables and paths into the shell. Necissary to add the conda executable to the path here.

.bashrc are specific to bash, whereas .profile is read by many shells in the absence of their own shell-specific config files.

The idea behind this was that one-time setup was done by .profile (or shell-specific version thereof), and per-shell stuff by .bashrc.


Variables

All variables are strings!

Access variables with $...

When you call a function, you can supply it data and options with space separated arguments ($1, $2 etc) and with a stdin.

Supply by arguments simply by writing the strings separated by spaces after the command word for example, the argument is ‘documents’. Inside the function, this first argument will be referenced with $1.

cd documents

Inside the cd function it will assign the variable with $1:

cd() {
    path = $1
	echo $path 
	
	.....
}

You can get to other variables with:

  • $a a variable named ‘a’
  • $? status code of last command
  • $# count of variables provided
  • $1 first positional variable
  • $2 second positional variable
  • $0 tells you the source of the function, but it depends on where it is defined:
    • contains the string -bash if the function is defined in the interactive terminal or it was ‘sourced’ into the profile of the interactive terminal, such as source ./.bashrc.
    • contains a string of the relative path to the file e.g. ./filename.sh if referenced inside a script. Doesn’t necessarily need to have #!/bin/bash at the top of the file.

Assign Variables with =

You don’t need to enclose the string in quotes if there are no special characters:

$ a=hello

If there are spaces or other special characters, you need to use "" or ''.

Single Quotes '...'

Single quotes make a completely raw string '...'. This is equivalent to r'...' in python

$ a='$100,000 USD'

Double Quotes "..."

Double quotes escape the space: ` ` and some other special characters, but still treat the following special characters:

  • $: Variable expansion or parameter expansion is performed.
  • `: Backticks are used for command substitution.
  • \: Backslash is used to escape other special characters within double quotes.
  • !: In interactive shells, it’s used for history expansion.
  • ": Finally, double quote must be escaped, or it will end the string.
$ a="$variable `pwd`"

Numbers

Vanilla Bash cannot represent numbers, only strings and 0-255 integer exit codes. You can do basic integer addition and subtraction.

Integer addition and subtraction with number strings with $(())

$ a=1
$ b=2
$ echo $((a+b))
3

It cannot even handle decimal numbers or scientific notation


Functions

Functions return exit codes BUT, they assign the contents of the echo command to the variable

Calling a function

Just write the name of the function.

$ a

Defining a function

The simplest function is the following:

a(){ command;}
  • a the name, a.k.a. handle of this function
  • () indicates that this is a function. The are always empty!
  • { ...;} is the command block. There must be a space after the opening brace!
  • Line break characters are irrelevant. the ; is the ‘separator’. All commands terminate with ; inside a function!

Function Exit codes

The return in functions is always an integer value from 0 to 255. 0 means success, 1 means generic error.

The return value is the exit code, the echo value is assigned to variables!

Unless you explicitly use the exit command with a specific exit code like exit 1 or exit 2 (or any other non-zero value), the default exit code for a script or program when it reaches the end of its execution is 0. This convention is used in many programming languages and shell scripts to indicate successful execution.

0 # always success. Default and means the script ran without errors. 
1 # generic error
2 to n # specific error. onwards are custom exit codes for bash that you generally associate with a message to indicate specific outcomes.
non

In an if statement, you literally just put the function:

if my_function; then 
	echo "Success, exit code 0" 
else 
	 echo "Failure, exit code non-zero" 
fi

$? gets the exit code of the previous function.

$ afunction
a=$?

Standard Output

Bash function inputs and outputs are only strings (arguments, stdin, stout and stderr) and 0-255 integers (exit codes). That’s it. String or integer. And 99% of the time you will be concerned with the strings.

The following sends the string “some text” to standard output (stout):

$ echo "some text"
some text

It appears, visibly, in the terminal.

You can send the stdout to a file by putting > after the command. This is called ‘redirecting’:

$ echo "some text" > test.txt

Standard Input

Send to stdin of a function

The most common way to send a stdin is via the pipe operator in the formatcommand1 | command2. Where command 1 sends its standard output to the standard input of command 2.

After the end of a command, Use < filename to read in a file name, <<EOF......EOF to read a multiline string (where …. is the multiline string) or finally <<<"string" to send a single line string.

Read stdin from within a function

Use a=$(</dev/stdin). This only works inside a function. It doesn’t make sense alone in the interactive terminal, because there is nothing to feed it stdin.

The command read a , will only read the first line into the string into the variable ‘a’.

To read a multiline output, you need to use a loop:

while IFS= read -r line; do
    a+="$line\n"
done

In Unix-like systems, /dev/stdin is a symbolic link to the standard input of the current process, not a shared or global input channel that spans across different terminal sessions. So, when you echo text to /dev/stdin in one terminal, it’s not going to be received by a read command waiting for input in another terminal. Each terminal’s /dev/stdin is unique to that specific terminal session.

In summary, you can send stdin to a function or code-block by piping a previous command, ( | ), file redirection (<), here string (<<<) or heredoc (<<). This will set the stdin of that code block to whatever you give it. You read each line from the stdin from within the function with the read command.

If you send another stdin to a loop, or a code block, it will only apply to that loop or code block.

Standard Error (stderr) echo "a" >&2

There is a second (and only a second) text output string called stderr. This also always appears visibly in terminal like standard output.

Access stderr with 2>

After calling a command, to differentiate stdout from stderr, you need to redirect to a file.

command > file.txt only redirects stdout to a file (equal to 1>)

command 2> file.txt only redirects stderr to a file

command &> file.txt redirects both to a file

Send to stderr

When writing a function, to send a string to the stderr, you use the echo, but instead of leaving the end empty, you place a >&2 after the end of the line:

For example:

echo "some text" >&2

(The default is actually >&1 which sends to stdout. You can write this if you want to be clear)

Suppress stderr with /dev/null

If you want to suppress the echo of stderr so that it never outputs, the convention is to direct it to the system file /dev/null.

sonecommand 2>/dev/null

Conditions and Equality

Conditions are done in bash by picking up on the exit code of a function inside the if condition.

Booleans

The strings ‘true’ and ‘false’ evaluate to an exit code of 0 and 1 respectively:

$ false; echo "$?"
1

$ true; echo "$?"
0

Equality a.k.a. test

Check equalities in bash by setting an exit code, which can be detected on the very next line afterwards in the $? variable.

syntax [ $a = $b ] is equivalent to test $a = $b

For example:

# False: exit code 1
$ [ 0 = 1 ]; echo $?
1

# True: exit code 0
$ [ 1 = 1 ]; echo $?
0

Note. Very important that you must put spaces between every word in the [ $a = $b ], as [$a=$b] will not work.

Likewise, using the other syntax :

# False: exit code 1
$ test 0 = 1; echo $?
1

# True: exit code 0
$ test 1 = 1 ; echo $?
0

You enclose an equality statement in [...]. This sends an exit code of either 0 (true), or 1 (false) when you which is picked up by the if function in the $? variable

Comparisons

Either;

  • [ ... ] or [[ ... ]], for string comparisons or file existence tests. [[ ... ]] is like the [ but has enhanced capabilities like regex matching and is less prone to errors with unquoted strings; Needs the $ syntax to refer to variables, for example [ $a = $a ]
    • = is equality, = is assignment is not possible
  • or (( ... )) for__ arithmetic comparisons__. This is unique and unique to putting anything else in front of an if , in that it is evaluating the numeric result of the expression, NOT the exit code of the expression. Therefore true = non-zero and false = 0 (the opposite to any other exprerssion). There are different syntaxes;
    • Don’t access variables with $a syntax. You can write (( a > a )) for example.
    • == is equality, = is assignment

Using the arithmetic expression you can do variable assignment:

$ (( a = 2 ))

$ echo $(( a + 2 ))
4
  • Use $(( ... )) when you need the result of the arithmetic operation.
  • Use (( ... )) for evaluating expressions, especially in control structures, without directly needing the result of the arithmetic operation.

Linebreaks can be replaced by ;

In Bash scripting, there are a few specific scenarios where a line break is necessary and cannot be replaced by a semicolon (;):

  1. Shebang Line: The shebang (#!/bin/bash) at the beginning of a script must be followed by a line break. A semicolon won’t work here because the shebang needs to be at the very beginning of the script, and anything following it on the same line is considered part of the interpreter path.

  2. Line Continuation: When a command is split across multiple lines using a backslash (\) for readability, a line break is necessary. The backslash tells the shell that the command continues on the next line. A semicolon in this case would indicate the end of a command, not its continuation.

  3. Here Documents: In a here document (a form of I/O redirection to feed a command list into an interactive program), the token that terminates the here document must be at the beginning of a line. A semicolon would not serve the same purpose.

  4. Comments: If you’re writing a comment (#), everything on the line after the # symbol is part of the comment. A line break is necessary to end the comment and start a new command. A semicolon would simply be included in the comment.

  5. Case Statements: In a case statement, the double semicolon (;;) is used to terminate each pattern-action pair. While this is a form of semicolon usage, a line break is typically used for readability and to separate different patterns.

  6. Function Definitions: While you can define a function using semicolons, it is customary and more readable to use line breaks, especially when the function body contains multiple statements.

In these cases, line breaks are either syntactically necessary or strongly preferred for readability and clarity.

Sub-Shell { ... }

Take this point less function: echo "text"|read a; echo $a;. It doesn’t return “text” because the a variables is assigned inside a sub-shell with the read function.

To retain the variable from within the sub-shell, you need to enclose the section in { }: echo "a"| { read a; echo $a ;}