Bash Scripting: Difference between revisions
mNo edit summary |
|||
| (22 intermediate revisions by the same user not shown) | |||
| Line 1: | Line 1: | ||
== | ==Useful functions== | ||
Here are some common functions and commands I use in my scripts. | |||
{| class="wikitable" | |||
!Function | |||
!Code | |||
|- | |||
|Use this to ensure the script runs relative to its location. | |||
This is useful if your script uses relative paths. | |||
|{{highlight|lang=bash|code= | |||
#!/bin/bash | |||
cd "$(dirname "$0")" | |||
}} | |||
|- | |||
|Find the maximum of two numbers. You can implement min by flipping the operands. | |||
|{{highlight|lang=bash|code= | |||
max (){ | |||
echo $(($1>$2 ? $1 : $2)) | |||
<nowiki>}</nowiki> | |||
}} | |||
|- | |||
|Similar to the {{code|die}} command in PHP which prints out a message before stopping execution. | |||
'''Note that this will not work inside a subshell''' as it will just quit the subshell rather than the script. To get around this, you may need to kill the parent PID instead. | |||
|<syntaxhighlight lang="bash"> | |||
function die { | |||
echo $@ | |||
exit | |||
} | |||
</syntaxhighlight> | |||
|- | |||
|Indents text piped to the function. | |||
|{{highlight|lang=terminal|code= | |||
# Supports up to 10 tabs | |||
function Indent (){ | |||
Tabs="\t\t\t\t\t\t\t\t\t\t" | |||
sed "s/^/${Tabs:0:$((2*$1))}/g" | |||
} | |||
echo "test" {{!}} Indent 1 | |||
test | |||
echo "test" {{!}} Indent 2 | |||
test | |||
}} | |||
|- | |||
|Extracting the file name and file extension from a path | |||
|{{highlight|lang=bash|code= | |||
Path="/etc/resolv.conf" | |||
# Get path parts | |||
Directory=$(dirname $Path) # /etc | |||
Filename=$(basename $Path) # resolv.conf | |||
# Get filename parts | |||
Name="${Filename%.*}" # resolv | |||
Ext="${Filename##*.}" # conf | |||
}} | |||
|- | |||
|Skip the first N lines from an output | |||
|{{highlight|lang=terminal|code= | |||
## -n +N outputs starting on line N. | |||
## Eg. To skip the first 2 lines, we start on line 3: | |||
# tail -n+3 | |||
}} | |||
|- | |||
|Get the current Unix timestamp | |||
|{{Highlight | |||
| code = $ date +"%s" | |||
1596137957 | |||
| lang = terminal | |||
}} | |||
|- | |||
|Unix timestamp to human readable time. | |||
You can use either <code>awk</code> with <code>strftime</code>, or with <code>date</code>. | |||
|{{highlight|lang=terminal|code= | |||
$ echo 1355514777 {{!}} awk '{ print strftime("%c", $1) }' | |||
Fri 14 Dec 2012 12:52:57 PM MST | |||
}}{{Highlight | |||
| code = $ date +"%c" -d @15996137957 | |||
Thu 30 Jul 2020 01:39:17 PM MDT | |||
| lang = terminal | |||
}} | |||
|- | |||
|Get a file's last modified time in Unix timestamp | |||
|<code>stat -c %Y ''filename''</code> | |||
|- | |||
|Convert a multi-line list into a comma delimited string | |||
|<code>''cat list''<nowiki> | paste -s -d, -</nowiki></code> | |||
|- | |||
|Get all IPv4 addresses on this host | |||
|<code><nowiki>ip a | grep -ohE 'inet [0-9.]{7,15}'</nowiki></code> | |||
|- | |||
|Get the URL from <a href="..."> | |||
|<code>''echo '<a href="asdf">'<nowiki/>''<nowiki> | grep -Eo 'href="[^\"]+"' | sed 's/href="\(.*\)"/\1/'</nowiki></code> | |||
|- | |||
|Replace substring in Bash | |||
| | |||
{| class="wikitable" | |||
|- | |||
|Use <code>${variable/search_term/replace_term}</code>. Eg:{{highlight|lang=bash|code= | |||
Path="/etc/resolv.conf" | |||
# This returns /etc/hostname | |||
echo ${Path/resolv.conf/hostname<nowiki>}</nowiki> | |||
}} | |||
|} | |||
|} | |||
==Useful scripting patterns== | |||
===Get a specific column=== | |||
You can use either <code>awk</code> with delimiters specified with <code>-F</code>. | |||
{{Highlight | |||
| code = $ echo one two three four five {{!}} awk '{print $3}' | |||
three | |||
$ echo one,two,three,four,five {{!}} awk -F, '{print $3}' | |||
three | |||
| lang = terminal | |||
}} | |||
Or cut by a specific delimiter and select a specific column. This method cannot handle columns separated by more than one space. If you have neat columns, you'll need to pipe through <code>tr -s ' '</code> first which collapses (or squeezes) the given repeating characters down. | |||
{{Highlight | |||
| code = $ echo one two three four five {{!}} cut -d ' ' -f3 | |||
three | |||
$ echo "one two three four five" {{!}} tr -s ' ' {{!}} cut -d ' ' -f3 | |||
three | |||
$ echo one,two,three,four,five {{!}} cut -d ',' -f3 | |||
three | |||
| lang = terminal | |||
}}Or use the bash built-in function <code>read</code> which can take in a list of variables. Variable assignment by piping to <code>read</code> works in zsh but will only work if used within a subshell in bash. Keep this behavior in mind if you choose to use this method of separating out words in a string. For example:{{Highlight | |||
| code = ## Basic variable assignment with read | |||
$ echo one two three four five {{!}} while read _ second _; do echo $second ; done | |||
two | |||
## zsh variable assignment works without a subshell/loop | |||
% echo one two three four five {{!}} read _ second _ | |||
% echo $second | |||
two | |||
| lang = terminal | |||
}}The last variable contains all remaining columns. Unwanted columns can be named repeatedly with <code>_</code> (like in golang) and ignored.{{Highlight | |||
| code = ## Remember, to get only the 3rd column, we need to read the 4th column out. | |||
% echo one two three four five {{!}} read _ _ third; echo $third | |||
three four five | |||
% echo one two three four five {{!}} read _ _ third _ ; echo $third | |||
three | |||
## Delimiters specified with IFS | |||
% echo one,two,three,four,five {{!}} IFS=',' read _ _ third _ ; echo $third | |||
three | |||
| lang = terminal | |||
}}In terms of speed, be aware that using <code>awk</code> or <code>tr+cut</code> are actual commands that spawns a sub-process whereas read is a bash built-in function. As a result, it's always faster to use read to parse out data, especially if in a loop. If you are looping and using <code>awk</code> or <code>cut</code>, perhaps consider replacing it with the bash <code>read</code> equivalent. For example: | |||
{{Highlight | |||
| code = ## Super slow! Invokes awk 2 times per line | |||
$ w {{!}} tail -n +3 {{!}} while read i ; do | |||
Username=$(echo "$i" {{!}} awk '{print $1}') | |||
Source=$(echo "$i" {{!}} awk '{print $3}') | |||
echo $Username $Source | |||
done | |||
## Instead do something like this! | |||
$ w {{!}} tail -n +3 {{!}} while read Username _ Source _ ; do | |||
echo $Username $Source | |||
done | |||
| lang = terminal | |||
}} | |||
===Preserve header while sorting without using a temporary file=== | |||
You can sort an output on a specific column while preserving the header and without needing a temporary file by using a shim function that reads and prints the first line before passing the rest of the output down the pipe. Define this function: | |||
{{Highlight | |||
| code = # print the header (the first line of input) | |||
# and then run the specified command on the body (the rest of the input) | |||
# use it in a pipeline, e.g. ps {{!}} body grep somepattern | |||
body() { | |||
IFS= read -r header | |||
printf '%s\n' "$header" | |||
"$@" | |||
} | |||
| lang = bash | |||
}} | |||
Then use this shim body function with the desired sort function. For example: | |||
{{Highlight | |||
| code = $ top -b -n 1 {{!}} head -n 15 {{!}} tail -n +7 \ | |||
{{!}} body sort -k 5 -rn | |||
| lang = terminal | |||
}} | |||
Credit to https://unix.stackexchange.com/questions/11856/sort-but-keep-header-line-at-the-top for this neat trick. | |||
==Testing things in Bash== | |||
{{code|test}} is a bash built-in function to add conditionals in your script. {{code|[}} is an alias of the {{code|test}} command and are analogous. | {{code|test}} is a bash built-in function to add conditionals in your script. {{code|[}} is an alias of the {{code|test}} command and are analogous. | ||
| Line 16: | Line 204: | ||
{| class="wikitable" | {| class="wikitable" | ||
! width="125px" | Operator | ! width="125px" |Operator | ||
! Description | !Description | ||
|- | |- | ||
! {{code|-e}} | !{{code|-e}} | ||
| file exists | |file exists | ||
| | | | ||
|- | |- | ||
! {{code|-a}} | !{{code|-a}} | ||
| file exists (deprecated) | |file exists (deprecated) | ||
This is identical in effect to {{code|-e}} | This is identical in effect to {{code|-e}} | ||
|- | |- | ||
! {{code|-f}} | !{{code|-f}} | ||
| file is a regular file (not a directory or device file) | |file is a regular file (not a directory or device file) | ||
|- | |- | ||
! {{code|-s}} | !{{code|-s}} | ||
| file is not zero size | |file is not zero size | ||
|- | |- | ||
! {{code|-d}} | !{{code|-d}} | ||
| file is a directory | |file is a directory | ||
|- | |- | ||
! {{code|-b}} | !{{code|-b}} | ||
| file is a block device. Eg. {{code|/dev/sda}}. | |file is a block device. Eg. {{code|/dev/sda}}. | ||
|- | |- | ||
! {{code|-c}} | !{{code|-c}} | ||
| file is a character device. Eg. {{code|/dev/tty0}}. | |file is a character device. Eg. {{code|/dev/tty0}}. | ||
|- | |- | ||
! {{code|-p}} | !{{code|-p}} | ||
| file is a pipe. Eg. {{code|/dev/fd/0}}. | |file is a pipe. Eg. {{code|/dev/fd/0}}. | ||
|- | |- | ||
! {{code|-h}} | !{{code|-h}} | ||
| file is a symbolic link | |file is a symbolic link | ||
|- | |- | ||
! {{code|-L}} | !{{code|-L}} | ||
| file is a symbolic link | |file is a symbolic link | ||
|- | |- | ||
! {{code|-S}} | !{{code|-S}} | ||
| file is a socket | |file is a socket | ||
|- | |- | ||
! {{code|-t}} | !{{code|-t}} | ||
| file (descriptor) is associated with a terminal device | |file (descriptor) is associated with a terminal device | ||
This test option may be used to check whether the stdin [ -t 0 ] or stdout [ -t 1 ] in a given script is a terminal. | This test option may be used to check whether the stdin [ -t 0 ] or stdout [ -t 1 ] in a given script is a terminal. | ||
|- | |- | ||
! {{code|-r}} | !{{code|-r}} | ||
| file has read permission (for the user running the test) | |file has read permission (for the user running the test) | ||
|- | |- | ||
! {{code|-w}} | !{{code|-w}} | ||
| file has write permission (for the user running the test) | |file has write permission (for the user running the test) | ||
|- | |- | ||
! {{code|-x}} | !{{code|-x}} | ||
| file has execute permission (for the user running the test) | |file has execute permission (for the user running the test) | ||
|- | |- | ||
! {{code|-g}} | !{{code|-g}} | ||
| file or directory has set-group-id (sgid) flag set | |file or directory has set-group-id (sgid) flag set | ||
If a directory has the sgid flag set, then a file created within that directory belongs to the group that owns the directory. | If a directory has the sgid flag set, then a file created within that directory belongs to the group that owns the directory. | ||
|- | |- | ||
! {{code|-u}} | !{{code|-u}} | ||
| file has set-user-id (suid) flag set | |file has set-user-id (suid) flag set | ||
|- | |- | ||
! {{code|-k}} | !{{code|-k}} | ||
| file or directory has sticky bit set | |file or directory has sticky bit set | ||
* If set on a file, that file will be kept in cache memory, for quicker access. | *If set on a file, that file will be kept in cache memory, for quicker access. | ||
* If set on a directory, it restricts write permission. Setting the sticky bit adds a t to the permissions on the file or directory listing. This restricts altering or deleting specific files in that directory to the owner of those files. | *If set on a directory, it restricts write permission. Setting the sticky bit adds a t to the permissions on the file or directory listing. This restricts altering or deleting specific files in that directory to the owner of those files. | ||
|- | |- | ||
! {{code|-O}} | !{{code|-O}} | ||
| you are owner of file | |you are owner of file | ||
|- | |- | ||
! {{code|-G}} | !{{code|-G}} | ||
| group-id of file same as yours | |group-id of file same as yours | ||
|- | |- | ||
! {{code|-N}} | !{{code|-N}} | ||
| file modified since it was last read | |file modified since it was last read | ||
|- | |- | ||
! {{code|f1 -nt f2}} | !{{code|f1 -nt f2}} | ||
| file f1 is newer than f2 | |file f1 is newer than f2 | ||
|- | |- | ||
! {{code|f1 -ot f2}} | !{{code|f1 -ot f2}} | ||
| file f1 is older than f2 | |file f1 is older than f2 | ||
|- | |- | ||
! {{code|f1 -ef f2}} | !{{code|f1 -ef f2}} | ||
| files f1 and f2 are hard links to the same file | |files f1 and f2 are hard links to the same file | ||
|- | |- | ||
! {{code|!}} | !{{code|!}} | ||
| "not" -- reverses the sense of the tests above (returns true if condition absent). | |"not" -- reverses the sense of the tests above (returns true if condition absent). | ||
|} | |} | ||
=== | ===Integers=== | ||
Integer tests, with {{code|test}} or inside single brackets. | Integer tests, with {{code|test}} or inside single brackets. | ||
| Line 121: | Line 309: | ||
{| class="wikitable" | {| class="wikitable" | ||
! width="125px" | Operator | ! width="125px" |Operator | ||
! Description | !Description | ||
|- | |- | ||
! {{code|-eq}} | !{{code|-eq}} | ||
| is equal to | |is equal to | ||
|- | |- | ||
|- | |- | ||
! {{code|-ne}} | !{{code|-ne}} | ||
| is not equal to | |is not equal to | ||
|- | |- | ||
! {{code|-gt}} | !{{code|-gt}} | ||
| is greater than | |is greater than | ||
|- | |- | ||
! {{code|-ge}} | !{{code|-ge}} | ||
| is greater than or equal to | |is greater than or equal to | ||
|- | |- | ||
! {{code|-lt}} | !{{code|-lt}} | ||
| is less than | |is less than | ||
|- | |- | ||
! {{code|-le}} | !{{code|-le}} | ||
| is less than or equal to | |is less than or equal to | ||
|} | |} | ||
Integer tests can | Integer tests can also be done inside double parentheses. Eg. | ||
{{highlight|lang=terminal|code= | {{highlight|lang=terminal|code= | ||
$ ((2==2)) && echo equal || echo not equal | $ ((2==2)) && echo equal || echo not equal | ||
| Line 154: | Line 342: | ||
{| class="wikitable" | {| class="wikitable" | ||
! width="125px" | Operator | ! width="125px" |Operator | ||
! Description | !Description | ||
|- | |- | ||
! {{code|1===}} | !{{code|1===}} | ||
| is equal | |is equal | ||
|- | |- | ||
! {{code|<}} | !{{code|<}} | ||
| is less than | |is less than | ||
|- | |- | ||
! {{code|1=<=}} | !{{code|1=<=}} | ||
| is less than or equal to | |is less than or equal to | ||
|- | |- | ||
! {{code|>}} | !{{code|>}} | ||
| is greater than | |is greater than | ||
|- | |- | ||
! {{code|1=>=}} | !{{code|1=>=}} | ||
| is greater than or equal to | |is greater than or equal to | ||
|} | |} | ||
=== Strings === | ===Strings=== | ||
{| class="wikitable" | {| class="wikitable" | ||
! width="125px" | Operator | ! width="125px" |Operator | ||
! Description | !Description | ||
|- | |- | ||
! {{code|-z}} | !{{code|-z}} | ||
| string is null, that is, has zero length | |string is null, that is, has zero length | ||
|- | |- | ||
! {{code|-n}} | !{{code|-n}} | ||
| string is not null. | |string is not null. | ||
|- | |- | ||
! {{code|1==}} | !{{code|1==}} | ||
| is equal to | |is equal to | ||
{{highlight|lang=terminal|code= | {{highlight|lang=terminal|code= | ||
| Line 196: | Line 384: | ||
}} | }} | ||
|- | |- | ||
! {{code|1===}} | !{{code|1===}} | ||
| is equal to | |is equal to | ||
{{highlight|lang=terminal|code= | {{highlight|lang=terminal|code= | ||
| Line 208: | Line 396: | ||
This operator inside a double bracket {{code|[[ ]]}} construct does pattern matching. | This operator inside a double bracket {{code|[[ ]]}} construct does pattern matching. | ||
|- | |- | ||
! {{code|1=!=}} | !{{code|1=!=}} | ||
| is not equal to | |is not equal to | ||
{{highlight|lang=terminal|code= | {{highlight|lang=terminal|code= | ||
| Line 219: | Line 407: | ||
This operator inside a double bracket {{code|[[ ]]}} construct does pattern matching. | This operator inside a double bracket {{code|[[ ]]}} construct does pattern matching. | ||
|- | |- | ||
! {{code|1==~}} | !{{code|1==~}} | ||
| Match using regex | |Match using regex | ||
{{highlight|lang=terminal|code= | {{highlight|lang=terminal|code= | ||
| Line 231: | Line 419: | ||
fi | fi | ||
}} | }} | ||
|} | |}<br /> | ||
</ | |||
==Command Substitution== | |||
== Command Substitution == | |||
Command substitution allows output from a command to be substituted in place of the command itself in a script. Commands can be enclosed within backticks {{code|`command`}} or the POSIX compatible form {{code|$(command)}}. | Command substitution allows output from a command to be substituted in place of the command itself in a script. Commands can be enclosed within backticks {{code|`command`}} or the POSIX compatible form {{code|$(command)}}. | ||
{{highlight|lang=terminal|code= | {{highlight|lang=terminal|code= | ||
| Line 336: | Line 431: | ||
Do not confuse {{code|$(command)}} with {{code|$((expression))}} which is used for arithmetic expansion. | Do not confuse {{code|$(command)}} with {{code|$((expression))}} which is used for arithmetic expansion. | ||
=== Nested Substitution === | ===Nested Substitution=== | ||
If you are trying to do something similar to: | If you are trying to do something similar to: | ||
{{highlight|lang=bash|code= | {{highlight|lang=bash|code= | ||
| Line 348: | Line 443: | ||
== Arithmetic Operations == | ==Arithmetic Operations== | ||
Do integer arithmetic operations on variables within double brackets. For example: | Do integer arithmetic operations on variables within double brackets. For example: | ||
{{highlight|lang=terminal|code= | {{highlight|lang=terminal|code= | ||
| Line 360: | Line 455: | ||
Bitwise operators also work: | Bitwise operators also work: | ||
^ (XOR) | (OR) & (AND) | ^ (XOR) | (OR) & (AND) << (Shift left) >> (Shift right) | ||
== Control Blocks == | ==Control Blocks== | ||
=== Switch === | ===Switch=== | ||
{{highlight|lang=terminal|code= | {{highlight|lang=terminal|code= | ||
read -p "What do you want to do?: " Option | read -p "What do you want to do?: " Option | ||
| Line 381: | Line 476: | ||
}} | }} | ||
== | ==See Also== | ||
*https://www.tldp.org/LDP/abs/html/comparison-ops.html | |||
* https://www.tldp.org/LDP/abs/html/comparison-ops.html | *http://sipb.mit.edu/doc/safe-shell/ | ||
*http://mywiki.wooledge.org/BashFAQ | |||
*https://github.com/dylanaraps/pure-sh-bible | |||
{{Navbox Linux}}[[Category:Linux]] | {{Navbox Linux}} | ||
[[Category:Linux]] | |||
Latest revision as of 23:53, 16 September 2022
Useful functions
Here are some common functions and commands I use in my scripts.
| Function | Code | |
|---|---|---|
| Use this to ensure the script runs relative to its location.
This is useful if your script uses relative paths. |
#!/bin/bash
cd "$(dirname "$0")"
| |
| Find the maximum of two numbers. You can implement min by flipping the operands. | max (){
echo $(($1>$2 ? $1 : $2))
}
| |
Similar to the die command in PHP which prints out a message before stopping execution.
Note that this will not work inside a subshell as it will just quit the subshell rather than the script. To get around this, you may need to kill the parent PID instead. |
function die {
echo $@
exit
}
| |
| Indents text piped to the function. | # Supports up to 10 tabs
function Indent (){
Tabs="\t\t\t\t\t\t\t\t\t\t"
sed "s/^/${Tabs:0:$((2*$1))}/g"
}
echo "test" | Indent 1
test
echo "test" | Indent 2
test
| |
| Extracting the file name and file extension from a path | Path="/etc/resolv.conf"
# Get path parts
Directory=$(dirname $Path) # /etc
Filename=$(basename $Path) # resolv.conf
# Get filename parts
Name="${Filename%.*}" # resolv
Ext="${Filename##*.}" # conf
| |
| Skip the first N lines from an output | ## -n +N outputs starting on line N.
## Eg. To skip the first 2 lines, we start on line 3:
# tail -n+3
| |
| Get the current Unix timestamp | $ date +"%s"
1596137957
| |
| Unix timestamp to human readable time.
You can use either |
$ echo 1355514777 | awk '{ print strftime("%c", $1) }'
Fri 14 Dec 2012 12:52:57 PM MST
$ date +"%c" -d @15996137957
Thu 30 Jul 2020 01:39:17 PM MDT
| |
| Get a file's last modified time in Unix timestamp | stat -c %Y filename
| |
| Convert a multi-line list into a comma delimited string | cat list | paste -s -d, -
| |
| Get all IPv4 addresses on this host | ip a | grep -ohE 'inet [0-9.]{7,15}'
| |
| Get the URL from <a href="..."> | echo '<a href="asdf">' | grep -Eo 'href="[^\"]+"' | sed 's/href="\(.*\)"/\1/'
| |
| Replace substring in Bash |
|
Useful scripting patterns
Get a specific column
You can use either awk with delimiters specified with -F.
$ echo one two three four five | awk '{print $3}'
three
$ echo one,two,three,four,five | awk -F, '{print $3}'
three
Or cut by a specific delimiter and select a specific column. This method cannot handle columns separated by more than one space. If you have neat columns, you'll need to pipe through tr -s ' ' first which collapses (or squeezes) the given repeating characters down.
$ echo one two three four five | cut -d ' ' -f3
three
$ echo "one two three four five" | tr -s ' ' | cut -d ' ' -f3
three
$ echo one,two,three,four,five | cut -d ',' -f3
three
Or use the bash built-in function read which can take in a list of variables. Variable assignment by piping to read works in zsh but will only work if used within a subshell in bash. Keep this behavior in mind if you choose to use this method of separating out words in a string. For example:
## Basic variable assignment with read
$ echo one two three four five | while read _ second _; do echo $second ; done
two
## zsh variable assignment works without a subshell/loop
% echo one two three four five | read _ second _
% echo $second
two
The last variable contains all remaining columns. Unwanted columns can be named repeatedly with _ (like in golang) and ignored.
## Remember, to get only the 3rd column, we need to read the 4th column out.
% echo one two three four five | read _ _ third; echo $third
three four five
% echo one two three four five | read _ _ third _ ; echo $third
three
## Delimiters specified with IFS
% echo one,two,three,four,five | IFS=',' read _ _ third _ ; echo $third
three
In terms of speed, be aware that using awk or tr+cut are actual commands that spawns a sub-process whereas read is a bash built-in function. As a result, it's always faster to use read to parse out data, especially if in a loop. If you are looping and using awk or cut, perhaps consider replacing it with the bash read equivalent. For example:
## Super slow! Invokes awk 2 times per line
$ w | tail -n +3 | while read i ; do
Username=$(echo "$i" | awk '{print $1}')
Source=$(echo "$i" | awk '{print $3}')
echo $Username $Source
done
## Instead do something like this!
$ w | tail -n +3 | while read Username _ Source _ ; do
echo $Username $Source
done
Preserve header while sorting without using a temporary file
You can sort an output on a specific column while preserving the header and without needing a temporary file by using a shim function that reads and prints the first line before passing the rest of the output down the pipe. Define this function:
# print the header (the first line of input)
# and then run the specified command on the body (the rest of the input)
# use it in a pipeline, e.g. ps | body grep somepattern
body() {
IFS= read -r header
printf '%s\n' "$header"
"$@"
}
Then use this shim body function with the desired sort function. For example:
$ top -b -n 1 | head -n 15 | tail -n +7 \
| body sort -k 5 -rn
Credit to https://unix.stackexchange.com/questions/11856/sort-but-keep-header-line-at-the-top for this neat trick.
Testing things in Bash
test is a bash built-in function to add conditionals in your script. [ is an alias of the test command and are analogous.
Use the test operators listed below with test or a single bracket test condition like the following example.
$ test -e /etc/hosts && echo "File exists"
File exists
## Note that the white space around [ and ] and also the operator are required.
$ [ -e /etc/hosts ] && echo "File exists"
File exists
$ if [ -e /etc/hosts ] ; then echo "File exists" ; fi
File exists
| Operator | Description | |
|---|---|---|
-e
|
file exists | |
-a
|
file exists (deprecated)
This is identical in effect to | |
-f
|
file is a regular file (not a directory or device file) | |
-s
|
file is not zero size | |
-d
|
file is a directory | |
-b
|
file is a block device. Eg. /dev/sda.
| |
-c
|
file is a character device. Eg. /dev/tty0.
| |
-p
|
file is a pipe. Eg. /dev/fd/0.
| |
-h
|
file is a symbolic link | |
-L
|
file is a symbolic link | |
-S
|
file is a socket | |
-t
|
file (descriptor) is associated with a terminal device
This test option may be used to check whether the stdin [ -t 0 ] or stdout [ -t 1 ] in a given script is a terminal. | |
-r
|
file has read permission (for the user running the test) | |
-w
|
file has write permission (for the user running the test) | |
-x
|
file has execute permission (for the user running the test) | |
-g
|
file or directory has set-group-id (sgid) flag set
If a directory has the sgid flag set, then a file created within that directory belongs to the group that owns the directory. | |
-u
|
file has set-user-id (suid) flag set | |
-k
|
file or directory has sticky bit set
| |
-O
|
you are owner of file | |
-G
|
group-id of file same as yours | |
-N
|
file modified since it was last read | |
f1 -nt f2
|
file f1 is newer than f2 | |
f1 -ot f2
|
file f1 is older than f2 | |
f1 -ef f2
|
files f1 and f2 are hard links to the same file | |
!
|
"not" -- reverses the sense of the tests above (returns true if condition absent). |
Integers
Integer tests, with test or inside single brackets.
Eg.
$ test 2 -eq 2 && echo equal
| Operator | Description |
|---|---|
-eq
|
is equal to |
-ne
|
is not equal to |
-gt
|
is greater than |
-ge
|
is greater than or equal to |
-lt
|
is less than |
-le
|
is less than or equal to |
Integer tests can also be done inside double parentheses. Eg.
$ ((2==2)) && echo equal
| Operator | Description |
|---|---|
==
|
is equal |
<
|
is less than |
<=
|
is less than or equal to |
>
|
is greater than |
>=
|
is greater than or equal to |
Strings
| Operator | Description |
|---|---|
-z
|
string is null, that is, has zero length |
-n
|
string is not null. |
=
|
is equal to
## Does not work in zsh. Works only in sh and bash.
$ if [ "$a" = "$b" ] ; then
echo "Equal"
fi
|
==
|
is equal to
## Does not work in zsh. Works only in sh and bash.
$ if [ "$a" == "$b" ] ; then
echo "Equal"
fi
This operator inside a double bracket |
!=
|
is not equal to
$ if [ "$a" != "$b" ] ; then
echo "Not equal"
fi
This operator inside a double bracket |
=~
|
Match using regex
$ if [ zone36-ta =~ .*ta$ ] ; then
echo "matches"
fi
$ if [ zone36-ta =~ *ta ] ; then
echo "matches"
fi
|
Command Substitution
Command substitution allows output from a command to be substituted in place of the command itself in a script. Commands can be enclosed within backticks `command` or the POSIX compatible form $(command).
## Eg.
echo "Today is " $(date)
echo "Today is " `date`
Do not confuse $(command) with $((expression)) which is used for arithmetic expansion.
Nested Substitution
If you are trying to do something similar to:
Something=`basename `ls /home/*.txt``
You will get an error since the expansion is ambiguous. Instead, use the $(command) substitution instead:
Something=$(basename $(ls /home/*.txt))
Arithmetic Operations
Do integer arithmetic operations on variables within double brackets. For example:
Value=1
ValuePlusOne=$((Value + 1))
ValueTimes12=$((Value * 12))
The operators that are supported are:
+ - / * %
Bitwise operators also work:
^ (XOR) | (OR) & (AND) << (Shift left) >> (Shift right)
Control Blocks
Switch
read -p "What do you want to do?: " Option
case "$Option" in
"K")
install_new_kernel
die "Complete!"
;;
"H")
usage
;;
*)
die "Stopping installation."
;;
esac
See Also
- https://www.tldp.org/LDP/abs/html/comparison-ops.html
- http://sipb.mit.edu/doc/safe-shell/
- http://mywiki.wooledge.org/BashFAQ
- https://github.com/dylanaraps/pure-sh-bible