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.
# subshell command group
a=0
(a=1; echo "a="$a) # expected 1
echo "a=$a" # expected 0
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.
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.
# subshell command group
echo
a=0
{ a=1; echo "a=$a"; } # expected 1
echo "a=$a" # expected 1
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.
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.
# this works, oh my :/
echo
c=$(x=1; ((x+=1)); echo $x)
echo "c=$c" # expect 2
c=2
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.
# 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
~/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.
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:
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.
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. |
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.
#!/bin/bash
#read standard input
while read line
do
echo $line
done < /dev/stdin
>bash input.sh < test.txt
This is a test
Second line
Third line
End of file
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.
~/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--
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:
# 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
~/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