Shell Overview
===
###### tags: `OS / Ubuntu / shell script`
###### tags: `OS`, `Ubuntu`, `linux`, `command`, `shell script`, `sh`, `bash`, `shebang`, `hashband`, `string`, `if-else`, `for-loop`, `makefile`, `指令串接`
<br>
[TOC]
<br>
# Linux: Shell script
## 術語
### bash
[bash (Bourne-Again Shell) ](https://www.796t.com/content/1546346172.html)
### dash
[dash (Debian Almquist Shell)](https://www.796t.com/content/1546346172.html)
### sheband (hashband)
- `#`: hash
- `!`: bang [ʃəˋbæŋ]
- https://tw.dictionary.search.yahoo.com/search?p=bang
- int. 砰!
- vi. 發出砰的一聲;砰砰作響;
- vt. 砰地敲(或推,扔);猛擊,猛撞;
- shebang 是什麼?
[ChatGPT] shebang(也稱為 hashbang)是一種在腳本文件的第一行使用的特殊註釋語法,用於指定解釋器的路徑。它的格式是以井號(#)開頭,後面跟著一個嘆號(!),然後是解釋器的路徑。在執行腳本時,操作系統會讀取 shebang 行,並使用指定的解釋器來解釋執行該腳本。例如,一個使用 shebang 的 Python 腳本可能會在第一行寫上 #!/usr/bin/python,以指定使用 Python 解釋器來運行該腳本。這樣就可以直接執行腳本文件而無需顯式地調用解釋器。
- 範例1
```bash
#! /bin/bash
```
- 範例2: `echo_test.sh`
```bash
#! /bin/bash
echo -e 'a\nb\nc'
```
```
$ sh echo_test.sh
$ bash echo_test.sh
$ cdmod +x echo_test.sh; ./echo_test.sh
```
- 範例3: `echo_test.sh`
```bash
#! /bin/sh
echo -e 'a\nb\nc'
```
```
$ sh echo_test.sh
$ bash echo_test.sh
$ cdmod +x echo_test.sh; ./echo_test.sh
```
- 參考資料
- [Shell Scripting for Beginners – How to Write Bash Scripts in Linux](https://www.freecodecamp.org/news/shell-scripting-crash-course-how-to-write-bash-scripts-in-linux/)
- [Python 文件頂部的 #!/user/bin/env python 是什麼意思](https://ithelp.ithome.com.tw/articles/10309816)
<br>
<hr>
<br>
## 模式
### 登入 Shell vs. 非登入 Shell
- ### 登入 Shell:
由登入系統時啟動(例如,透過 SSH 或控制台)。
會讀取 `/etc/profile`、`~/.bash_profile`、`~/.bash_login`、`~/.profile` 等配置文件。
- ### 非登入 Shell:
由登入 Shell 或其他程序啟動(例如,您在登入 Shell 中執行 `bash`)。
會讀取 `~/.bashrc` 配置文件。
- ### status 差異
- **登入 Shell:**
```bash
$ shopt
...
login_shell on
...
```
- **非登入 Shell:**
```bash
$ shopt
...
login_shell off
...
```
- ### cmd 差異
- **登入 Shell:**
```bash
$ echo $0
-bash
$ ps -p $$ -o args=
-bash
```
```
$ p() { basename "$(pwd)"; }
-bash: syntax error near unexpected token `('
```
- **非登入 Shell:**
```bash
$ bash
$ echo $0
bash
$ ps -p $$ -o args=
bash
```
```
$ p() { basename "$(pwd)"; }
$ p
```
<br>
<hr>
<br>
## 入門
- [Shell Scripting for Beginners – How to Write Bash Scripts in Linux](https://www.freecodecamp.org/news/shell-scripting-crash-course-how-to-write-bash-scripts-in-linux/)
<br>
<hr>
<br>
## 特殊變數
### 教學資料
- ### [Shell特殊变量:Shell $0, $#, $*, $@, $?, $$和命令行参数](http://c.biancheng.net/cpp/view/2739.html)
| 变量 | 含义 |
|----|----|
| `$0` | 当前脚本的文件名 |
| `$n` | 传递给脚本或函数的参数。n 是一个数字,表示第几个参数。例如,第一个参数是$1,第二个参数是$2。 |
| `$#` | 传递给脚本或函数的参数个数。 |
| `$*` | 传递给脚本或函数的所有参数。 |
| `$@` | 传递给脚本或函数的所有参数。被双引号(" ")包含时,与 $* 稍有不同,下面将会讲到。 |
| `$?` | 上个命令的退出状态,或函数的返回值。 |
| `$$` | 当前Shell进程ID。对于 Shell 脚本,就是这些脚本所在的进程ID。 |
- :warning: **`$*` 和 `$@` 的区别**
- `$*` 把所有的參數,打包成 1 個,可以說是 all-in-one
- ### [Bash wait Command with Examples](https://phoenixnap.com/kb/bash-wait-command)
| 变量 | 含义 |
|----|----|
| `&` | ampersand sign (and 符號), indicats a background job |
| `$!` | 最後背景程序的 PID |
| `$?` | 最後程序的離開狀態 |

- 比較 `$$` 和 `$!`
- `$$` 是當前 SHELL 程序的 PID
- `$!` 是最後一個背景程序的 PID
- ### [操作 `${@}`](https://stackoverflow.com/questions/1537673)
1. open new file and edit it: vim r.sh:
```
echo "params only 2 : ${@:2:1}"
echo "params 2 and 3 : ${@:2:2}"
echo "params all from 2: ${@:2:99}"
echo "params all from 2: ${@:2}"
```
2. run it:
```
$ chmod u+x r.sh
$ ./r.sh 1 2 3 4 5 6 7 8 9 10
```
3. the result is:
```
params only 2 : 2
params 2 and 3 : 2 3
params all from 2: 2 3 4 5 6 7 8 9 10
params all from 2: 2 3 4 5 6 7 8 9 1
```
### 實作範例
- `myshell.sh`
```sh
echo "Hello, myshell!"
echo '----------------------'
echo "\$@: $@"
echo "\$*: $*"
echo "\$#: $#"
echo "\$0: $0"
echo "\$1: $1"
echo "\$2: $2"
echo "\$3: $3"
echo "\$4: $4"
echo "\$5: $5"
echo "\$?: $?"
echo "\$$: $$"
echo '----------------------'
echo "\${1}: ${1}"
echo '${1:+"$@"}:' ${1:+"$@"}
echo '${1:+"$*"}:' ${1:+"$*"}
echo '${1:+"$#"}:' ${1:+"$#"}
echo '----------------------'
echo 'for var in $@'
for var in $@
do
echo "$var"
done
echo
echo 'for var in '"'"'$@'"'"''
for var in '$@'
do
echo "$var"
done
echo
echo 'for var in "$@"'
for var in "$@"
do
echo "$var"
done
echo '----------------------'
echo 'for var in $*'
for var in $*
do
echo "$var"
done
echo
echo 'for var in '"'"'$*'"'"''
for var in '$*'
do
echo "$var"
done
echo
echo 'for var in "$*"'
for var in "$*"
do
echo "$var"
done
```
- ### 測試1:`sh myshell.sh aa 'bb \" cc' "dd \" ee" ff`
```
Hello, myshell!
----------------------
$@: aa bb \" cc dd " ee ff
$*: aa bb \" cc dd " ee ff
$#: 4
$0: myshell.sh
$1: aa
$2: bb \" cc
$3: dd " ee
$4: ff
$5:
$?: 0
$$: 3374206
----------------------
${1}: aa
${1:+"$@"}: aa bb \" cc dd " ee ff
${1:+"$*"}: aa bb \" cc dd " ee ff
${1:+"$#"}: 4
----------------------
for var in $@ <--- 展開不正確
aa
bb
\"
cc
dd
"
ee
ff
for var in '$@'
$@
for var in "$@"
aa
bb \" cc
dd " ee
ff
----------------------
for var in $* <--- 展開不正確
aa
bb
\"
cc
dd
"
ee
ff
for var in '$*'
$*
for var in "$*"
aa bb \" cc dd " ee ff
```
- :warning: `'$@'` `'$*'` 是純字串
- :warning: `$@` `$*` 正確用法應該是要加雙引號
- :warning: `"$*"` 用途不明
- :warning: 底下有沒有加 `: (colon)`,目前測起來沒有差別
```sh
echo '${1+"$@"}:' ${1+"$@"}
echo '${1+"$*"}:' ${1+"$*"}
echo '${1+"$#"}:' ${1+"$#"}
```
- ### 測試2:`sh myshell.sh`
```
Hello, myshell!
----------------------
$@:
$*:
$#: 0
$0: myshell.sh
$1:
$2:
$3:
$4:
$5:
$?: 0
$$: 3374612
----------------------
${1}:
${1:+"$@"}:
${1:+"$*"}:
${1:+"$#"}:
----------------------
for var in $@
for var in '$@'
$@
for var in "$@"
----------------------
for var in $*
for var in '$*'
$*
for var in "$*"
```
- ### 測試3:透過 `bash -c ...` 測試 `$@` 和 `$*` 之間的差異
[](https://i.imgur.com/rC7S76P.png)
[](https://i.imgur.com/Upwppe3.png)
- ### 參考資料
- [How to escape single quotes within single quoted strings](https://stackoverflow.com/questions/1250079)
- [${1:+"$@"} in /bin/sh](https://stackoverflow.com/questions/154625/)
- If `$1` exists and is not an empty string, then substitute the quoted list of arguments.
如果 `$1` 存在且不是空字串,則替換成引用的參數清單
- [How to iterate over arguments in a Bash script](https://stackoverflow.com/questions/255898)
- [Add arguments to 'bash -c'](https://unix.stackexchange.com/questions/144514/)
<br>
<hr>
<br>
## 重導參數研究 (forward parameters)
> #dockerfile, forward parameters, forward arguments,
### 實作1
- ### `Dockerfile`
```dockerfile=
FROM ubuntu:18.04
COPY preshell.sh /usr/local/bin/preshell
RUN chmod +x /usr/local/bin/preshell
ENTRYPOINT ["preshell"]
COPY myshell.sh /usr/local/bin/myshell
RUN chmod +x /usr/local/bin/myshell
CMD ["bash"]
```
- ### `preshell.sh`
```bash
#!/bin/bash
echo 'raw info:'
echo '---------------------------------'
echo "command: $@"
echo "\$0: $0"
echo "\$1: $1"
echo "\$2: $2"
echo "\$3: $3"
echo "\$4: $4"
echo "\$5: $5"
echo "(more, total=$#)"
echo
# get the user's script name
script=$1
# skip the script name, then get the remaining arguments
# i.e. preshell.sh echo a b c
# => preshell.sh a b c (where echo is dropped)
shift
# escape chars for \ and " (where \ goes first)
args=""
for arg in "$@"
do
# replace all occurrences, use ${parameter//pattern/string}:
arg=${arg//\\/\\\\}
arg=${arg//\"/\\\"}
args="$args \"$arg\""
done
# pattern:
# sh -c '$0 "$@"' $script $arg1 $arg2 $arg3 ...
# e.g.
# sh -c '$0 "$@"' echo aa 'bb' "cc" 'dd ee' "ff gg" 'hh'"'"'"ii' "jj'\"kk"
# sh -c '$0 "$@"' echo -e "WX\bYZ\tZ\nA"
# where:
# - ' in 'hh'"ii' string needs to be escaped as '""'
# - skip_arg0 does nothing
# - if you use $0 as script name, you will get preshell.sh instead of $script
#
cmd="sh -c '$script "'"$@"'"' skip_arg0 $args"
echo "\$script: $script"
echo "\$args: $args"
echo "\$cmd: $cmd"
echo ">>>>>>>>>>>"
echo "\$cmd: $cmd"
eval $cmd
echo "<<<<<<<<<<<"
echo "exiting"
exit
```
- `$@` 就是指 args (argument list),不包含 $0

- ### `myshell.sh`
```sh
#!/bin/sh
echo "Hello, myshell!"
echo '----------------------'
echo "\$@: $@"
echo "\$*: $*"
echo "\$#: $#"
echo "\$0: $0"
echo "\$1: $1"
echo "\$2: $2"
echo "\$3: $3"
echo "\$4: $4"
echo "\$5: $5 (more)"
echo "\$#: $#"
echo "\$?: $?"
echo "\$$: $$"
echo '----------------------'
echo "\${1}: ${1}"
echo '${1:+"$@"}:' ${1:+"$@"}
echo '${1:+"$*"}:' ${1:+"$*"}
echo '${1:+"$#"}:' ${1:+"$#"}
echo '${1+"$@"}:' ${1+"$@"}
echo '${1+"$*"}:' ${1+"$*"}
echo '${1+"$#"}:' ${1+"$#"}
echo '----------------------'
echo 'for var in "$@"'
for var in "$@"
do
echo "$var"
done
echo '----------------------'
echo 'for var in "$*"'
for var in "$*"
do
echo "$var"
done
```
- ### 操作流程
```
$ docker build -t ubuntu-test:18.04 ./
```
```
$ docker run --rm -it ubuntu-test:18.04 ls -ls
```
```
$ docker run --rm -it ubuntu-test:18.04 myshell aa 'bb' "cc" 'dd ee' "ff gg" 'hh'" ' "' " ii' "jj ' \" kk"
```
- ### debug 專用
`tmp.sh`
```bash
#!/bin/bash
script=echo
echo $@
args=""
for arg in "$@"
do
# replace all occurrences, use ${parameter//pattern/string}:
arg=${arg//\\/\\\\}
arg=${arg//\"/\\\"}
args="$args \"$arg\""
done
echo $args
cmd="sh -c '$script "'"$@"'"' bash $args"
echo $cmd
eval $cmd
```
執行方式:`$ bash tmp.sh a b c`
只能使用 bash,因為有使用到 find-replace 用法 (sh 無提供)
### 參考資料
- [Add arguments to 'bash -c'](https://unix.stackexchange.com/questions/144514/) :+1: :+1: :100:
```
/bin/bash -c 'echo "$0" "$1"' foo bar
/bin/bash -c 'echo "$@"' bash foo bar
/usr/bin/env -- "ls" "-l"
```
執行結果
```
foo bar
foo bar
(ls -l 略)
```
若加入 `shift` 指令,則:
```
/bin/bash -c 'shift; echo "$0" "$1"' foo bar
/bin/bash -c 'shift; echo "$@"' bash foo bar
```
執行結果
```
foo
bar
```
- [Joining bash arguments into single string with spaces](https://unix.stackexchange.com/questions/197792)
### 實作2 (更簡單)
- [[官方][Docker] ENTRYPOINT](https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#entrypoint)
```
#!/bin/bash
set -e
if [ "$1" = 'postgres' ]; then
chown -R postgres "$PGDATA"
if [ -z "$(ls -A "$PGDATA")" ]; then
gosu postgres initdb
fi
exec gosu postgres "$@"
fi
exec "$@"
```
<br>
<hr>
<br>
## 變數範圍
- [Set a parent shell's variable from a subshell](https://stackoverflow.com/questions/15541321/set-a-parent-shells-variable-from-a-subshell)
```bash
a=3
(a=4)
echo $a #=3
```
```
a=3
{ a=4;}
echo $a #=4
```
- [how to export VARs from a subshell to a parent shell?](https://serverfault.com/questions/37796/how-to-export-vars-from-a-subshell-to-a-parent-shell)
<br>
<hr>
<br>
## 方法參數
### file exist or not exist
```bash=
if [ -e filename ]; then echo 'exist'; else echo 'not exist'; fi
# 等效於
if test -e filename; then echo 'exist'; else echo 'not exist'; fi
```
```bash=
if [ -f azure-parabricks-test_key.pem ]; then
echo 'exist'
else
echo 'NOT exist'
fi
```
```bash=
if [ -z azure-parabricks-test_key.pem ]; then
echo 'NOT exist'
else
echo 'exist'
fi
```
- [How to Check if a File or Directory Exists in Bash](https://linuxize.com/post/bash-check-if-file-exists/) :+1: :100:
- [What does "if [ -e $name ]" mean? Where $name is a path to a directory](https://unix.stackexchange.com/questions/127743/)
<br>
### variable exist or not exist
- unset, set (empty, non-rmpty)?
- `${var+x}`: 當 `var` 有任何指派(=)動作,就會有 `x`(字串)
- `${var:+x}`: 當 `var` 有任何指派(=)動作 & 長度大於0,就會有 `x`(字串)
- `[ -z $var ]`: 當 `var` 有任何指派(=)動作 & trim()後長度大於0,就會是 false,其餘為 true
`[ -z ${var} ] && echo echo unset_or_empty || echo not_empty`
- `$ if __condition__; then echo "unset [${p}][${p+HOME}][${p:+HOME}]"; else echo "set [$p][${p+HOME}][${p:+HOME}]"; fi`
- 條件測試:`-z ${p}`
| condition | -z -> true | -z -> false |
| --------- | ---------------- | ----------- |
| unset p | unset [][][] | |
| p= | unset [][HOME][] | |
| p='' | unset [][HOME][] | |
| p="" | unset [][HOME][] | |
| p=' ' | unset [ ][HOME][HOME] | |
| p=" " | unset [ ][HOME][HOME] | |
| p="param" | | set [param][HOME][HOME] |
- 條件測試:`-z ${p+x}`
| condition | -z -> true | -z -> false |
| --------- | ---------------- | ----------- |
| unset p | unset [][][] | |
| p= | | set [][HOME][] |
| p='' | | set [][HOME][] |
| p="" | | set [][HOME][] |
| p=' ' | | set [ ][HOME][HOME] |
| p=" " | | set [ ][HOME][HOME] |
| p="param" | | set [param][HOME][HOME] |
- 條件測試:`-z ${p:+x}`
| condition | -z -> true | -z -> false |
| --------- | ---------------- | ----------- |
| unset p | unset [][][] | |
| p= | unset [][HOME][] | |
| p='' | unset [][HOME][] | |
| p="" | unset [][HOME][] | |
| p=' ' | | set [ ][HOME][HOME] |
| p=" " | | set [ ][HOME][HOME] |
| p="param" | | set [param][HOME][HOME] |
- [How to check if a variable is set in Bash](https://stackoverflow.com/questions/3601515)
正確方式
```
if [ -z ${var+x} ]; then echo "var is unset"; else echo "var is set to '$var'"; fi
```
錯誤方式
```
if [ -z "$var" ]; then echo "var is blank"; else echo "var is set to '$var'"; fi
```
- "$var" 無法區分:[1]變數未被設定 [2]變數是空值
> it doesn't distinguish between a variable that is unset and a variable that is set to the empty string.
- [2.6.2 Parameter Expansion](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_06_02)
- [How To Bash Shell Find Out If a Variable Is Empty Or Not](https://www.cyberciti.biz/faq/unix-linux-bash-script-check-if-variable-is-empty/)
> #if-else, 三元運算子
```
[[ ! -z "$var" ]] && echo "Not empty" || echo "Empty"
```
- [Linux Shell指令碼攻略:shell中各種括號()、(())、[]、[[]]、{}的作用](https://codertw.com/%E7%A8%8B%E5%BC%8F%E8%AA%9E%E8%A8%80/544464/)
- 特殊替換
`${var:-string},${var: string},${var:=string},${var:?string}`
- 模式匹配替換
`${var%pattern},${var%%pattern},${var#pattern},${var##pattern}`
- 字串提取和替換
`${var:num},${var:num1:num2},${var/pattern/pattern},${var//pattern/pattern}`
### 教學
- ### [linux 下shell中if的“-e,-d,-f”是什么意思](https://blog.csdn.net/superbfly/article/details/49274889)
文件表达式
`-e filename` 如果 filename存在,则为真
`-d filename` 如果 filename为目录,则为真
`-f filename` 如果 filename为常规文件,则为真
`-L filename` 如果 filename为符号链接,则为真
`-r filename` 如果 filename可读,则为真
`-w filename` 如果 filename可写,则为真
`-x filename` 如果 filename可执行,则为真
`-s filename` 如果文件长度不为0,则为真
`-h filename` 如果文件是软链接,则为真
`filename1 -nt filename2` 如果 filename1比 filename2新,则为真。
`filename1 -ot filename2` 如果 filename1比 filename2旧,则为真。
<br>
<hr>
<br>
## 語法
### if-then-else-fi
```sh=
h=/home/tj
if [ $h = "/home/tj" ]; then
echo 'exist'
else
echo 'not exist'
fi
```
- 如果沒有 ';' 會有 error
`tmp.sh: 5: tmp.sh: Syntax error: "else" unexpected (expecting "then")`
亦可改成:`then` 切到下一行
```sh=
h=/home/tj
if [ $h = "/home/tj" ]
then
echo 'exist'
else
echo 'not exist'
fi
```
```sh=
h=/home/tj
if [ $h = "/home/tj" ];
then
echo 'exist'
else
echo 'not exist'
fi
```
- [Shell test命令(Shell [])详解,附带所有选项及说明](http://c.biancheng.net/view/2742.html)
<br>
### if-statement
| 條件 | 說明 | 範例結果 |
|--------------------|----|---------|
| `-n "$VAR"` | 判斷 `$VAR` 是否「非空」 | `"abc"` → ✅;`""` → ❌ |
| `-z "$VAR"` | 判斷 `$VAR` 是否「為空」 | `""` → ✅;`"abc"` → ❌ |
| `"$VAR" = "value"` | 判斷是否等於特定值 | `"abc" = "abc"` → ✅ |
| 符號 | 含義 | 推測來源 |
|------|-----------------------|------------------------|
| `-n` | **non-zero length** | `n` = nonempty 或 non-null |
| `-z` | **zero length** | `z` = zero-length |
```bash=
# 判斷是否有傳 CMD 參數
if [ -n "$(CMD)" ]; then
echo "你有傳 CMD=$(CMD)"
else
echo "你沒有傳 CMD"
fi
```
<br>
<br>
<hr>
<br>
### bash vs sh
- [运行shell脚本时报错"\[\[ : not found"解决方法](https://www.cnblogs.com/han-1034683568/p/7211392.html)
- bash与sh是有区别的,两者是不同的命令,且bash是sh的增强版,而"[[]]"是bash脚本中的命令,因此在执行时,使用sh命令会报错,将sh替换为bash命令即可:
<br>
<hr>
<br>
## 特殊語法
### 槽狀用法 `'basename $(pwd)'`
```bash=
alias dirname='basename `pwd`' # case1
alias dirname='basename $(pwd)' # case2
alias dirname="basename `pwd`" # case3
alias dirname="basename $(pwd)" # case4
```
```bash=
# case5
dirname() {
basename $(pwd)
}
```
- **雙引號**:命令替換在**定義別名時**發生。
- **單引號**:命令替換*不會**在定義或執行別名時發生。
- **建議**:使用函數比別名更靈活,特別是當涉及到命令替換和參數時。
<br>
### 數值計算
- 操作範例
```bash
# 透過 $((運算式))
$ echo $(((1+2)*3))
9
$ echo $((5/3))
1
```
```bash
$ s=1
$ e=12
$ echo $(( e - s ))
11
$ echo $(( ( e - s ) / 10 ))
1
$ echo $(( ( e - s ) / 5 ))
2
```
- 時間戳記 timestamp
```baash
date && start_time=`date +%s` && echo "start_time:" $start_time
sleep 12 # do something
date && end_time=`date +%s` && echo "end_time:" $end_time
total=$((end_time - start_time))
time_h=$((total/3600))
time_m=$(( (total - time_h*3600) / 60 ))
time_s=$(( total - time_h*3600 - time_m*60 ))
echo "Total time: ${time_h}h ${time_m}m ${time_s}s"
```
<br>
<hr>
<br>
## 回傳值
### Unix 訊號
- [[wiki] Unix訊號](https://zh.wikipedia.org/wiki/Unix%E4%BF%A1%E5%8F%B7)
### exited with code ###
- [Question: My batch job exited with code ###. What does that mean?](http://www.bu.edu/tech/files/text/batchcode.txt) :+1:
> 資料來源:[C 語言為什麼是 int main() { ... }](https://blog.moli.rocks/2016/12/15/why-should-main-return-in-c/)
- the system sets the exit code to 128 + \<signal number\>
- 137: 128 + SIGKILL(9)
- [[FreeBSD] preferable exit codes for programs](https://www.freebsd.org/cgi/man.cgi?query=sysexits&apropos=0&sektion=0&manpath=FreeBSD+4.3-RELEASE&format=html)
<br>
### 11 (SIGSEGV)
- Parabricks / pbrun germline: Received signal: 11

<br>
### 137
- **[Docker-compose exit code is 137 when there is no OOM exception](https://stackoverflow.com/questions/59296801/)**
- According to this post, the exit code of 137 can be due to two main issues.
- The container received a docker stop and the app is not gracefully handling SIGTERM
- The container has run out of memory (OOM).
- **[What causes a container to exit with code 137?](https://janetpanic.com/what-causes-a-container-to-exit-with-code-137/)**
- What causes error 137 in Docker
> Error 137 in Docker denotes that the container was ‘KILL’ed by ‘oom-killer’ (Out of Memory). This happens when there isn’t enough memory in the container for running the process.
- How to resolve error 137 in Docker
> So the first option is to identify the cause of this memory usage.
- **[Process finished with exit code 137 in PyCharm](https://stackoverflow.com/questions/43268156)**
- Exit code 137 means that your process was killed by (signal 9) `SIGKILL`
<br>
<hr>
<br>
## 指令串接
## 指令串接/討論
## `;` 與 `&&` 的差異與使用時機
- ### gpt-4o (2025/04/16)
在 Makefile 或 shell 腳本中,`;` 與 `&&` 都可用來串接多個指令,但它們的行為略有不同。
---
### 🧠 差異比較
| 比較項目 | `;` (semicolon) | `&&` (and operator) |
|----------|------------------|----------------------|
| 執行順序 | 逐行執行,不管前面成功或失敗 | **僅在前一行成功時才執行下一行** |
| 錯誤處理 | 不管 exit code,後面仍執行 | 若前一行失敗(exit code ≠ 0),**中止** |
| 常見用途 | 簡單串接,不關心錯誤 | 嚴謹控制流程、防止錯誤後續執行 |
---
### 🔍 範例比較
**使用 `;`(不管前一行成敗)**
```bash
echo hello; false; echo world
```
輸出:
```
hello
world
```
即使 `false` 會回傳錯誤(exit code 1),`echo world` 還是執行了。
---
**使用 `&&`(前一行成功才繼續)**
```bash
echo hello && false && echo world
```
輸出:
```
hello
```
這裡 `false` 失敗了,導致 `echo world` 不會執行。
---
### ✅ 在 Makefile 中的應用
你原本的程式碼如下:
```makefile
run-app:
export RUN_MODE=$(RUN_MODE); \
export SERVER_URL=$(SERVER_URL); \
export SHOW_SWAGGER=$(SHOW_SWAGGER); \
export SERVER_PORT=$(SERVER_PORT); \
$(IMAGE_DIR)/entrypoint.sh
```
因為你只是要「設定一連串環境變數後,執行一個腳本」,用 `;` 是可以接受的,也比較常見。
但如果你希望當某個 `export` 失敗就中止,那可以改用 `&&`:
```makefile
run-app:
export RUN_MODE=$(RUN_MODE) && \
export SERVER_URL=$(SERVER_URL) && \
export SHOW_SWAGGER=$(SHOW_SWAGGER) && \
export SERVER_PORT=$(SERVER_PORT) && \
$(IMAGE_DIR)/entrypoint.sh
```
不過實際上 `export` 幾乎不會失敗(除非語法錯),所以 `;` 就夠用了。
---
### ✅ 若想更嚴謹控制流程(可選)
可以改用 `&&`,讓任一 export 失敗時就終止流程:
```makefile
run-app:
export RUN_MODE=$(RUN_MODE) && \
export SERVER_URL=$(SERVER_URL) && \
export SHOW_SWAGGER=$(SHOW_SWAGGER) && \
export SERVER_PORT=$(SERVER_PORT) && \
$(IMAGE_DIR)/entrypoint.sh
```
---
### ✅ 更簡潔寫法(推薦)
直接將變數寫在同一行執行:
```makefile
run-app:
RUN_MODE=$(RUN_MODE) SERVER_URL=$(SERVER_URL) \
SHOW_SWAGGER=$(SHOW_SWAGGER) SERVER_PORT=$(SERVER_PORT) \
$(IMAGE_DIR)/entrypoint.sh
```
這樣會在 **同一個 shell 環境中執行**,不需逐行 export,簡潔有效。
<br>
<hr>
<br>
## 特殊操作
### eval
使用 **單引號** 或 **雙引號** 皆可
```
cmd='echo pid=$$'
eval $cmd
```
```
cmd="echo pid=$$"
eval $cmd
```
執行結果
```
pid=862639
```
- [Run a string as a command within a Bash script](https://stackoverflow.com/questions/2355148/)
<br>
### shift
- [How do I forward parameters to other command in bash script?](https://stackoverflow.com/a/1537687/4359712)
`shifttest.sh`:
```
#!/bin/bash
echo $1
shift
echo $1 $2
```
`shifttest.sh 1 2 3` produces
```
1
2 3
```
- If you forward the arguments as `$1` without quoting them as `"$1"`, then the shell will perform word splitting, so e.g. `foo bar` will be forwarded as `foo` and `bar` separately.
- [shell: "can't shift that many" error](https://superuser.com/questions/897148/)
> 因為沒有多餘的參數可以讓你 shift
```sh
#tmp.sh
echo '$@: '"$@"
echo "\$#: $#"
shift
echo '$@: '"$@"
```
執行結果:
```
$ sh tmp.sh
$@:
$#: 0
tmp.sh: 3: shift: can't shift that many
```
```
$ sh tmp.sh 1
$@: 1
$#: 1
$@:
```
```
$ sh tmp.sh 1 2 3
$@: 1 2 3
$#: 3
$@: 2 3
```
解決辦法:
```sh=
echo '$@: '"$@"
echo "\$#: $#"
if [ $# -gt 0 ]; then
echo "do shift"
shift
fi
echo '$@: '"$@"
```
<br>
### 字串 string
- ### [Replace one substring for another string in shell script](https://stackoverflow.com/questions/13210880)
- ### 字串串接
```bash
x=123
y=abc
z=$x$y # z=${x}y, z=$x${y}, z=${x}${y}
echo $z
```
- [How to concatenate string variables in Bash](https://stackoverflow.com/questions/4181703)
- ### 子字串
> `${變數:起始索引:長度}`
```bash
x=0123456789
echo '${x:0:3}: '${x:0:3} # 012
echo '${x:7:3}: '${x:7:3} # 789
echo '${x:5:100}: '${x:5:100} # 56789
```
去頭
> `#$%` 的 `#`
> 語法:`${x` + `#` + `*`(萬用字元) + `尋找字元` + `}`
```bash
x=0123456789
echo ${x#*3} # 砍掉 0123,只留 456789
x=0011223344
echo ${x#*3} # 砍掉 0011223 (not greedy),只留 344
echo ${x##*3} # 砍掉 00112233 (greedy),只留 44
```
去尾
> `#$%` 的 `%`
> 語法:`${x` + `%` + `*`(萬用字元) + `尋找字元` + `}`
```bash
x=0123456789
echo ${x%3*} # 砍掉 3456789,只留 012
x=0011223344
echo ${x%3*} # 砍掉 344 (not greedy),只留 0011223
```
- [Extract substring in Bash](https://stackoverflow.com/questions/428109)
- ### 格式化
```bash
x=123
printf "%06d\n" $x
printf "%6d\n" $x
printf "%-6d\n" $x
```

```bash
x=0123456789
printf "%.0s\n" $x
printf "%.2s\n" $x # 01
printf "%.5s\n" $x # 01234
```
- [Linux printf command](https://www.computerhope.com/unix/uprintf.htm)
- ### 路徑處理
> 見「子字串」處理
- 取出檔案名稱:`${X##*/}`
- `#` 表示從前面開始找
- `#` 第二個`#`表示貪婪
- `*` 表示從前面開始吃掉任意字元,直到 `/` (最後一個,後面已經沒有 `/`)
- 取出當前路徑:`${X%/*}`
- `%` 表示從後面開始找
- `*` 表示從後面開始吃掉任意字元,直到 `/` (後面數來第一個,不貪婪)
- 完整範例
```bash
X=/home/tj/Downloads/docker/Dockerfile
echo "檔案名稱:${X##*/}"
echo "當前路徑:${X%/*}"
echo "當前路徑:${0}"
```
執行結果:
```
檔案名稱:Dockerfile
當前路徑:/home/tj/Downloads/docker
當前路徑:bash
```
- 參考資料
- [bash - how to find current shell command](https://stackoverflow.com/a/5077898/4359712)
- ### 字串比較
```
if [ $h = "/home/tj" ]; then
echo "case1:$h"
else
echo "case2"
fi
```
- 會有 error
`tmp.sh: 3: [: =: unexpected operator`
- 解決方法:要加雙引號
```
if [ "$h" = "/home/tj" ]; then
echo "case1:$h"
else
echo "case2"
fi
```
- [shell腳本報錯:"[: =: unary operator expected"](https://www.796t.com/content/1507882933.html)
- 究其原因,是因為如果變量STATUS值為空,那麽就成了 [ = "OK"] ,顯然 [ 和 "OK" 不相等並且缺少了 [ 符號,所以報了這樣的錯誤。
- 其他參考資料
- [Compare a string using sh shell]()
<br>
<hr>
<br>
## 注意事項
### 傳遞環境變數
傳遞環境變數,需以雙引號傳遞,不能用單引號
```sh
$ python test.py --PATH='$HOME' <--- 失敗
['test.py', '--PATH=$HOME']
$ python test.py --PATH='${HOME}' <--- 失敗
['test.py', '--PATH=${HOME}']
$ python test.py --PATH="$HOME"
['test.py', '--PATH=/home/tj']
$ python test.py --PATH="${HOME}"
['test.py', '--PATH=/home/tj']
```
<br>
<hr>
<br>
## 完整教學
- [[ITREAD] Shell 教程](https://www.itread01.com/study/linux-shell.html)
- [[鳥哥] 第十二章、學習 Shell Scripts](https://linux.vbird.org/linux_basic/centos7/0340bashshell-scripts.php)
<br>
<hr>
<br>
# Linux: Makefile
## 變數範圍
- [Makefile 語法簡介](https://sites.google.com/site/mymakefile/makefile-yu-fa-jian-jie) :+1: :100:
- [How to abort makefile if variable not set?](https://stackoverflow.com/questions/10858261/)
## API
- [Append date and time to an environment variable in linux makefile](https://stackoverflow.com/questions/1859113)
```makefile
$(shell operation)
```
```makefile=
LOGPATH = logs
LOGFILE = $(LOGPATH)/$(shell date --iso=seconds)
test_logfile:
echo $(LOGFILE)
sleep 2s
echo $(LOGFILE)
```
<br>
{%hackmd vaaMgNRPS4KGJDSFG0ZE0w %}