Sage-Code Laboratory
index<--

Command Groups

You can group a set of commands and execute them one by one into a sub-shell. When the last command is executed the sub-shell terminate. A commad group can produce output that can be captured in the main script.

Sub-shell group

A command group is enclosed in round brackets. Several commands are separated by semicollon ";" like this: (command1; command2; ... command n). Usually the last command in the group is producing an output using "echo" or "exit" that can be captured or ignored by the main script.

example
# subshell command group
a=0
(a=1; echo "a="$a)  # expected 1
echo "a=$a"         # expected 0
output
a=1
a=0

Note: The variable "a", is used inside of command group but the modification do not propagate outside of the sub-shell. You can inject values but you can't get values out. It's because the sub-shell is an independent process that creates a new execution context.

In process group

You can create a command group that run in the same context as the current Bash session. A new process is not created, so the commands operate in the same scope as the main script. In this case any alteration of variables inside the group will propagate out of the group.

examples
# subshell command group
echo
a=0
{ a=1; echo "a=$a"; }  # expected 1
echo "a=$a"            # expected 1
console
a=1
a=1

Note: Usig {} is a little bit different than (). There is a space that is mandatory between { and first command. Also, the list of command has to terminate with ";". Don't ask me why. After you learn these little details, it will work.

Capture the Output

The result of command group can be captured using symbol "=" or notation "$?". The command can be used for it's side effects. The output of a command group can be redirected.

examples
# this works, oh my :/
echo 
c=$(x=1; ((x+=1)); echo $x)
echo "c=$c" # expect 2
console
c=2

Chain of commands

We know every command return a status. This can be evaluated into a Boolean and is possible to be asses into a conditional. So we can use Boorlan operators: and, or, not to connect several commands into a single chain.

chain.sh
# Search file by name
read -p "Search file:" file

# with continuation symbol "\"
[[ -f "$file" ]] && \
  echo "Found" ||   \
  echo "Not found"

# with conditional
if [[ -e "$file" ]]
then
   echo "file exist"
else
   echo "file do not exist"
fi
console
~/bash-repl$ bash chain.sh
Search file:chain.sh
Found
file exist
~/bash-repl$ bash chain.sh
Search file:invalid.txt
Not found
file do not exist
~/bash-repl$ 

Notes: In example above we have a short script "chain.sh" that read the input for a file name as a string. If the file is found we have message "found" otherwise the message "not found". We have run this example two times in console and you can read the output. First time file is found, second time not found.

Output Redirectation

Every command can use input, output and error streams. Each stream has a number associated with it, called "file descriptor". Operating system where Bash is running should provide 3 stream descriptors:

Streams

Output redirection

Sometimes is useful to capture output into a real file or to get input from a real file. This is done using redirection. The basic redirection symbol is this: ">" or "<" and it can be combined with "&" or "|" and eventually a number. You can learn some patterns.

Examples:

Command Description
command > file Redirect output to a file. File is overwritten if already exists.
command >> file Append output to a file. File is created if does not already exists.
command < file Accept input for read from file_name. File must exist.

Input redirection

Some built-in commands accept data using standard input. To accept input redirection we use symbol "<". For example, in script, we can use a loop to read the input stream. Usualli the input redirectation comes from a file.

example
#!/bin/bash
#read standard input
while read line
do
 echo $line
done < /dev/stdin
console
>bash input.sh < test.txt
This is a test
Second line
Third line
End of file

Command Pipleines

You can connect one command output to next command input. The pipeline operator is a vertical bar "|" or ampersand "&". In the example below we have used "| more" to stop a long scroll. You can press space for next page or enter for next row.

console

~/bash-repl$ ls -l *.sh | more
-rw-r--r-- 1 runner runner  196 Sep 24 15:34 args.sh
-rw-r--r-- 1 runner runner  226 Sep 19 22:59 array.sh
-rw-r--r-- 1 runner runner  358 Sep 18 18:41 case.sh
-rw-r--r-- 1 runner runner   84 Sep 24 09:16 c-for.sh
-rw-r--r-- 1 runner runner  246 Sep 29 00:40 chain.sh
-rw-r--r-- 1 runner runner  210 Sep 25 22:54 colon.sh
-rw-r--r-- 1 runner runner  162 Oct  3 02:33 com-group.sh
-rw-r--r-- 1 runner runner  233 Oct  3 02:15 command.sh
--More--

Command Substiturion

A command can be replaced by its output. Command substitution can be used with a command group. We can use command substiturion for assignement expressions but also with string expressions. Command substitution has 2 forms:

example
# substitution in string
echo "Current folder: $(pwd)"

# substitution in expression
dir=$(ls *.sh)

# check content of result
for file in $dir
do
    echo $file;
done
# preferred shorthand in a loop
for file in *,sh
do
    echo $file;
done
console
~/bash-repl$ bash substitution.sh 
Current folder: /home/runner/bash-repl
args.sh
array.sh
case.sh
c-for.sh
chain.sh
colon.sh
com-group.sh
command.sh
conditionals.sh
decision.sh
demo.sh

Read next: Data Types