Common Operations
Check if a package has been installed
dpkg -llists all debian packages, including apt packages, not including snap, flatpak, AppImages.- or one can do
dpkg -l <package> dpkg -s nftablesprints the package status directly- An idiom to check for package, then install if necessary is:
dpkg -s <pkg> >/dev/null 2>&1 || (sudo apt-get update && sudo apt-get install <pkg>)
- or one can do
ssh -o StrictHostKeyChecking=accept-newwill automatically accept the host key without prompting, even for a fresh container. This option was introduced in OpenSSH 7.6 and automatically adds unknown host keys to~/.ssh/known_hostswithout user interaction.- create multiple dirs using shell brace expansion, not an array: {}, command
mkdir -p ~/docker/isaac-sim/{cache/main,cache/computecache,config,data,logs,pkg}- mkdir -p won’t overwrite a dir if it already exists
- Quick for loop:
1
for i in {1..10}; do echo "Run #$i"; done
Get the current dir path
- BASH_SOURCE vs $0:
BASH_SOURCEis a Bash array that holds the filenames of the scripts in the current “source call stack”.BASH_SOURCE[0]: the current script file), even if it was sourced.BASH_SOURCE[1],[2], etc. = the files that sourced it$0= the name of the top-level script you invoked (orbashif you’re in an interactive shell), and it does not change when yousourceanother file.
- When you run it without absolute path,
BASH_SOURCE[0]:does NOT have the absolute path either . When you run it - Example: if
main.shsourceslib/X.sh, then insideX.sh:1 2
$0: main.sh BASH_SOURCE[0]: x.sh
- So you normally use:
1
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
--is end of options marker:- it tells
dirname: stop parsing flags, everything after it is a path - protects you when your file name starts with a
-, like-my_script.sh
- it tells
- when do you create a subshell? like “$()”?
$()is called a parentheses group. It runs the grouped commands in a separate process in the current directory- Pipelines
cmd1 | cmd2also creates its own subshell
$(pwd)will give you the path of where you run this command. That is, if you do: ```bash cd /tmp /path/to/scripts/main.sh
$pwd will give /tmp
1
2
3
4
5
6
7
- `.` is source. if `dirname` resolves to `.`, it could be executed if you just do `dirname ...`
### Make an argument required
```bash
local run_dir="${1:?run_dir is required}"
- A variable can be empty or unset
$(1?my sentence): ? will triggermy sentenceif it’s unset. If the first argument is empty, it still wouldn’t${1:?run_dir is required}is triggered when $1 is either empty or unset
Write to a file or overwrite
1
2
echo "hello" > my_file #overwrite
echo "hello" >> my_file #append
Run some commands in background
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
(
while true; do
timestamp="$(date +%s)"
values="$(_read_udp_counters)"
# TODO
# echo $values
echo "$timestamp,$values">> "$csv_output"
sleep $INTERVAL
done
) &
udp_counter_pid=$!
...
kill $udp_counter_pid
wait $pid 2>/dev/null || true
- You need
&to run a group command in the background - you need
$!to grab the PID of the process waitwaits and reaps the zombie process.killjust sends the SIGTERM signal
Find ros2 package path
1
2
3
4
5
_find_pkg_path(){
local pkg_name="$1"
echo $(find ~/toolkitt_ws/src -path "*/quartermaster/*" -prune -o -name "$pkg_name" -type d -print -quit 2>/dev/null)
}
Error Handling
set -euo pipefail
-e (errexit): Exit immediately if any command returns a non-zero status (except in certain contexts like && )-u (nounset) Treat unset variables as an error and exit.-
-o pipefail (same as -e -o pipefail, often written -eo pipefail): In a pipeline (cmd1cmd2) return the exit status of the first failed command instead of the last one.
Trap an error
set -ewill exits the parent shell immediately upon an errortrap 'echo "Error occurred, but shell will not close"; return 1' ERRwill terminate the current shell, but won’t terminate the parent shell
Nounset mode
set -u enables the nounset mode, where using an undefined variable is treated as an error:
1
2
set -u
echo "$NAME"
If NAME was never defined: bash: NAME: unbound variable
In this case, the second case will still fault because we need an extra assignment: POSE_ESTIMATE_INSTALL_TRT="${POSE_ESTIMATE_INSTALL_TRT:-1}", or fallback assignment${VAR:=value} # fallback and assignment :
1
2
3
4
unset POSE_ESTIMATE_INSTALL_TRT
echo "${POSE_ESTIMATE_INSTALL_TRT:-1}" # prints 1
echo "$POSE_ESTIMATE_INSTALL_TRT" # error: unbound variable
Built-ins
Test
[[ -n STRING ]]- returns true when STRING is not empty[[ -z STRING ]]- returns true when STRING is empty- equivalent to
[[ ! -n STRING ]]
- equivalent to
[[ -d FILE ]]- returns true when FILE exists and is a directory
Regex
$asserts “end of line”, so it makes sure the regex contains strings end in a certain pattern:grep -E '\.(md|markdown)$'
Declare
The declare builtin is used to explictly declare variables
declare -r varis to declare a read-only variable
1
2
declare -r var="123"
var="456" # see "var: readonly variable"
declare -a indexed_arraydeclare -A associative_arraydeclare -i integer_valuedeclare -x exported_var="exported var"can also define and export a variable- It can also print all variable (custom & environment variables) values.
declare -p
Though a most variables (except for arrays) can be defined directly VAR=123, I found declare more “type-safe”.
Operators
for ((i=1; i < 100; ++i)) : (())is arithmatic evaluation. () is just grouping
Stdout and file descriptor
[[ -t FD ]] is to check if the terminal is connected to a file descriptor. Normally,
- FD = 0; stdin
- FD = 1: stdout
- FD = 2: stdout.
Therefore, one use case is to skip emitting ANSI escape codes when output is redirected
1
2
3
4
5
6
7
8
9
# print_msg_in_blue "hello" > log.txt
print_msg_in_blue() {
if [[ -t 1 ]]
printf '\033[34m%s\033[0m\n' "$*"
else
printf '%s\n' "$*"
fi
}
Source vs executing
1
2
export PATH="/something:$PATH"
cd /some/directory
./setup_env.sh executes a file in a new shell process. Environment variables disappear when the script finishes. However, if you do source ./setup_env.sh , the changes remain because the file is executed in the current shell.
One can check if a script is sourced or executed:
1
[[ -n "${BASH_SOURCE[0]-}" && "${BASH_SOURCE[0]}" != "$0" ]]
BASH_SOURCE is an array container the source filenames involved in the current call stack. $0 is the originally executed program or shell.
- In
./setup_env.sh, the values are:1 2
BASH_SOURCE[0] = ./setup_env.sh $0 = ./setup_env.sh
- However, in
source setup_env.sh, the values are1 2
BASH_SOURCE[0] = setup_env.sh $0 = bash
or in an interactive shell:
1 2
BASH_SOURCE[0] = setup_env.sh $0 = -bash