---
# System prepended metadata

title: Shell Overview
tags: [shell script, linux, OS, 指令串接, shebang, string, command, sh, if-else, OS / Ubuntu / shell script, bash, Ubuntu, for-loop, makefile, hashband]

---

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 |
    | `$?` | 最後程序的離開狀態 |
    
    ![](https://i.imgur.com/aANn59Z.png)
    - 比較 `$$` 和 `$!`
        - `$$` 是當前 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/rC7S76P.png)
    
    [![](https://i.imgur.com/Upwppe3.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
        ![](https://i.imgur.com/jXLPkl6.png)
- ### `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
    ![](https://i.imgur.com/7EntlNW.png)


<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
    ```
    ![](https://i.imgur.com/6ULWFnE.png)
    
    ```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 %}
