1. [ 居然是一个 Shell 命令, 位置在 /bin/[, 大概可以算冷知识了?

  2. [][[]] 中判断的时候使用 === 是等价的(不考虑特例, 就这么记好了).

  3. [] 可以用于判断, 左右需要加空格. 可以有多个判断条件, 判断条件必须是 -o, -a, 取反可以使用 !.

    []test 命令的另一种写法版本, 也是 POSIX 标准, 即下列写法完全等效:

      if test "$str1" = "$str2" 
      then
        ...
      fi
    
      if [ "$str1" = "$str2" ]  # 注意左右的空格
      then
        ...
      fi
    
  4. 判断可以使用 -eq, -ne, -le 等等.

    # this will echo true
    # "-o" means "or"
    if [ "string" -o "" ]; then
        echo true
    else
        echo false
    fi
    
    # this will echo false
    # "-a" means "and"
    if [ "string" -a "" ]; then
        echo true
    else
        echo false
    fi
    
    # this depends on file existence
    if [ -e filename ]; then
        echo exists
    else
        echo not exists
    fi
    
    # this will echo true
    if [ ! "" ]; then
        echo true
    else
        echo false
    fi
    
  5. [[]] 也可用于判断, 左右需加空格, 可以有多个判断条件, 判断条件可以是 ||, &&, 取反可以使用 !. [[]]ksh, bash 提供的扩展, 可以使用正则 =~.

    # this will echo true
    if [[ "string" || "" ]]; then
        echo true
    else
        echo false
    fi
    
    # this will echo false
    if [[ "string" && "" ]]; then
        echo true
    else
        echo false
    fi
    
    # this depends on file existence
    if [[ -e filename ]]; then
        echo exists
    else
        echo not exists
    fi
    
    # this will echo true
    if [[ ! "" ]]; then
        echo true
    else
        echo false
    fi
    
  6. {} (code block) 用于执行脚本命令, 在当前窗口执行, 会影响当前脚本, 左右必须留空(存疑, 此处经验证似乎不需要留空格), 语句结尾必须加分号 ; (或者 } 另起一行). 主要目的就是为了构造一个执行块, 让一系列语句作为一个整体一起执行(比如作为一个管道的一部分).

    一些注释:

    For historical reasons, the braces are treated as shell keywords: this means that they’re recognized only as the first symbol in a command. Practically speaking, this means that you must place the closing brace after a newline or after a semicolon.

    var=0
    {var=1;}
    echo $var # $var is 1 now
    
  7. () (subshell) 用于执行脚本命令, 新起窗口执行, 不影响当前脚本, 其中有 $ 的字符串被认为是变量, 执行变量展开. 左右不必留空, 语句结尾不必加分号 ;. 可以认为完全等同于 `command` 语法.

    var=0
    (var=1)
    echo $var # $var is still 0
    
  8. (()) 用于进行四则运算, 其中即使没有 $ 的字符串也会被认为是变量, 会执行变量展开, 基本可以替换 let "expression" 语法, 左右可以不留空格.

    expr 也可用于四则运算, 不过 Classic Shell Scripting 书中的评价是:

    The expr command is one of the few Unix commands that is poorly designed and hard to use. Although standardized by POSIX, its use in new programs is strongly discouraged, since there are other programs and facilities that do a better job.

    新式写法中, 几乎都已经采用 $(()) 的写法了.

    var=0
    ((var=1))
    echo $var # $var is 1 now
    
  9. 通常情况下, 无论 (), (()), 前面加 $ 符号意味着要取得其返回值, 而不加 $ 符号意味着只是让其简单做一个运算, 如同执行了一个 C 语言中的 void 类型函数, 无返回值.

    echo $(date) # echo the output of date command
    echo $((1))  # echo 1
    
    echo (date)  # this will lead error
    echo ((1))   # this will lead error
    
  10. for 循环的一些用法, 注意第二种 {} 左右并未留空格, 我也很奇怪为啥:

    # C style
    for ((i = 1; i <= 10; i++)); do
        echo $i
    done
    
    # Perl/Python style
    for i in {1..10}; do
        echo $i
    done
    
  11. 参数展开:
      ${varname:-word}  # 变量存在且不为空, 返回变量, 否则返回 word
      ${varname:=word}  # 变量存在且不为空, 返回变量, 否则返回 word, 且赋值变量为 word
      ${varname:?message}  # 变量存在且不为空, 返回变量, 否则打印 message, message 为空打印 parameter null or not set 
      ${varname:+word}  # 变量存在且不为空, 返回 word, 否则返回 null
    

    如果上述表达式中 : 不存在, 则仅判断变量是否存在, 不判断变量是否为空.

      path=/home/tolstoy/mem/long.file.name
    
      ${variable#pattern}  # delete shortest beginning
      ${path#/*/} Result: tolstoy/mem/long.file.name
    
      ${variable##pattern}  # delete longest beginning
      ${path##/*/} Result: long.file.name
    
      ${variable%pattern}  # delete shortest ending
      ${path%.*} Result: /home/tolstoy/mem/long.file
    
      ${variable%%pattern}  # delete longest ending
      ${path%%.*} Result: /home/tolstoy/mem/long
    
      $(date)      # Command Substitution, just like ``
      $((x += 1))  # Arithmetic Expansion, just like C language
    
      $(date)      # 等于 `date`, 结果是 Sun Apr 5 10:46:25 CST 2020
      $((date))      # 等于取变量 date 的值, 没有值就会得到 0
    
  12. Exit Status:
      Only the low-order eight bits are returned to the parent
      process, so an exit status greater than 255 is replaced by the remainder of that value
      divided by 256.
      Thus: exit 256 equals exit 0
    
      Behavior
      The default exit status used if none is supplied is the exit status of the last com-
      mand executed. If that is what you want, it is best to do this explicitly in the shell
      script:
      exit $?
    

    如果 if 后跟的是命令, 那么命令的 exit status 是 0 的时候, if 认为是成功的:

      if date; then  # exit status of `date` is 0
        echo OK    # thus OK echoed
      else
        echo NG
      fi
    
  13. the POSIX standard recommends the use of shell-level tests for multiple conditions, instead of the -a and -o operators. (We also recommend this.)
      if [ -f "$file" ] && ! [ -w "$file" ]
      then
        # $file exists and is a regular file, but is not writable
        echo $0: $file is not writable, giving up. >&2
        exit 1
      fi
    

    Arguments are required:

      if [ -f "$file" ] # ... Correct
      if [ -f $file ]   # ... Incorrect, for $file could be nothing, leads test received no argument
    

    有时候, - 可能会导致 test 命令出错, 所以:

      if [ "X$answer" = "Xyes" ]  # we use leading X in both quoted strings, X is arbitrary but traditional
    
  14. $*, $@ 都表示传入脚本或者函数的所有参数, 他们在 for 中展开的时候, 按空格展开, 如果某个参数本来就有空格, 就会被展开成两个参数.

    一般用双引号包裹起来再展开, 此时两者不一样, "$*" 被展开成一个字符串, "$@" 被展开成多个字符串, 其中微妙的区别如下:

      $ set -- hello "hi there" greetings  # Set new positional parameters
      $ echo there are $# total arguments  # Print the count
      there are 3 total arguments
    
      $ for i in $*  # Loop over arguments individually
      > do echo i is $i
      > done
      i is hello  # Note that embedded whitespace was lost
      i is hi
      i is there
      i is greetings
    
      $ for i in $@  # Without quotes, $* and $@ are the same
      > do echo i is $i
      > done
      i is hello
      i is hi
      i is there
      i is greetings
    
      $ for i in "$*"  # With quotes, $* is one string
      > do echo i is $i
      > done
      i is hello hi there greetings
    
      $ for i in "$@"  # With quotes, $@ preserves exact argument values
      > do echo i is $i
      > done
      i is hello
      i is hi there
      i is greetings
    
      $ shift  # Lop off the first argument
      $ echo there are now $# arguments # Prove that it's now gone
      there are now 2 arguments
    
      $ for i in "$@"
      > do echo i is $i
      > done
      i is hi there
      i is greetings
    
  15. 关于 getopts:

      # 如果 getops 得到了一个未知或者错误选项, $opt 将被置为 `?`
      # 顶头的 : 代表 getopts 不再打印默认的错误信息, 同时 $OPTARG 不再代表选项的参数, 而是出错的选项本身, $opt 依然在出错的时候被置为 `?`
      # 跟在某个选项后面的 :,  比如 f:, 代表 -f 选项是必须要参数的
      # $opt 是取到的选项, 比如 -f -v -q -l, 此时 - 被去掉, 只剩 f v q l
      # $OPTARG 是有参数选项的参数, 比如跟在 -f 后的那个值
    
      # $OPTIND 代表即将被处理的下一个参数的索引(INDex), 默认为 1, 主要用于 shift
      # 比如传入参数 -f somefile, $opt => f(第一位), $OPTARG => somefile(第二位), 此时 $OPTIND 将等于 3
    
      file= verbose= quiet= long=
      while getopts :f:vql opt
      do
          case $opt in Check option letter
          f) file=$OPTARG
              ;;  # 标记一个 case 选项的结束
          v) verbose=true
              quiet=
              ;;
          q) quiet=true
              verbose=
              ;;
          l) long=true
              ;;
          '?') echo "$0: invalid option -$OPTARG" >&2
          echo "Usage: $0 [-f file] [-vql] [files ...]" >&2
              exit 1
              ;;
          esac
      done
    
Comments
Write a Comment