Bash 使用简明笔记

概述

  Shell的最佳之处在于书写小型脚本非常自然快捷,而复杂的shell脚本经常出现可移植问题,因其依赖大量可能不具有跨平台性的辅助命令。现在,负责的胶合层程序几乎都由Perl和Python来完成,shell只是为最简单的包装器和系统初始化脚本而保留。—— 《Unix编程艺术》

bash的执行循环

  • a. 获得输入:文件、命令行或者shell终端
  • b. 词法解析:获得操作符和单词,解析引用和别名
  • c. 命令解析:将b中token解析为单个或者组合命令
  • d. 执行shell扩展
  • e. 处理必要的重定向
  • f. 执行命令
  • g. 可选地等待命令完成并获得其退出状态

login shell读取的配置文件:

  • a. /etc/profile 全局配置,它会读入/etc/bash.bashrc
  • b. ~/.bash_profile~/.bash_rc~/.bash_login 个人设定
  • c. /etc/inputrc bash的热键设定,比如[tab]键禁止Beep.
  • d. /etc/profile.d/*.sh 规范了bash操作接口的补全、颜色、语系,一些命令的别名等

字符串引用(quoting)

  • 转义字符:
  • 单引号:保留字符值
  • 双引号:保留字符值,除了 ‘$’、‘`’、‘\‘,!(历史扩展功能被打开后)
  • ANSI-C引用:\a、\b、\e、\n、\r、\t、'、"、\?、\nnn(八进制)、\xHH、\uHHHH(Unicode16)、\uHHHHHHHH(Unicode32)、\cx(控制x字符)

Shell命令

简单命令

  • 管道: | 或者 |& , “|&” 等价于 “2>&1 |”,将标准错误重定向到标准输出并一起通过管道连接到下一条命令的标准输入。管道里的所有命令都会在单独进程中运行,最后一条命令的退出状态作为整个pipeline的退出状态。
  • time 保留字
  • 命令序列:&&、||、;、&、newline
    1
    2
    3
    4
    newcomand || newcommand2 && newcommand3
    newcomand ; newcomand2 ; newcomand3
    # 注意控制结构中‘;’分割符的使用,使用该分隔符可以获得更紧凑的代码,
    # 不需要分行编写代码

控制结构和复杂命令

  • until循环
    1
    until test-commands; do consequent-commands; done
  • while循环
    1
    while test-commands; do consequent-commands; done
    1
    2
    3
    4
    5
    while IFS= read -r x; do
    do-something1 "$x" "config-$x"
    do-something2 < "$x"
    done < file | process-output

  • for 循环
    1
    2
    for [ [in [words …] ] ; ] do commands; done
    # words或被扩展
    1
    2
    # 类似于C语言
    for (( expr1 ; expr2 ; expr3 )) ; do commands ; done
  • if 语句
    1
    2
    3
    4
    5
    6
    if test-commands; then
    consequent-commands;
    [elif more-test-commands; then
    more-consequents;]
    [else alternate-consequents;]
    fi
  • case 语句
    1
    2
    3
    4
    5
    6
    7
    case word in
    [ [(] pattern [| pattern]…) command-list ;;]…
    esac
    # * 类似与else,如果前面所有的case项没有选中,则选中该项
    # ;; 会终止执行匹配项后剩余的待匹配项
    # ;& 会继续执行下一个(如果有的话)case项的命令
    # ;;& 会继续匹配剩下的case项
  • select 语句
    1
    2
    # 生成选择菜单
    select name [in words …]; do commands; done
  • ((expression)) 语句

  执行算术表达式,等价于 let “expression”,如果表达式非0,退出状态为0;否则,退出状态为1。

  • [[expression]] 语句

  如果条件表达式非0,退出状态为0;否则,退出状态为1。该语句与((…))都可以作为if结构的谓词判断部分。不支持字符分列和文件名扩展,支持 ~ 扩展、参数和变量扩展、算术扩展、命令扩展、过程扩展、去除引用。==、!=使用时右边的操作元为模式(pattern),pattern的任何部分都可能加引号。 =~ 操作符可以用于扩展正则表达式的模式匹配。

1
2
3
4
[[ $line =~ [[:space:]]*(a)?b ]]

pattern='[[:space:]]*(a)?b'
[[ $line =~ $pattern ]]
  • ( list ) 命令组语句
       将一组命令在一个子shell环境中执行,这与管道组成的pipeline不同。重定向会应用到该组所有的命令,输出也会重定向到一个流中去。特别注意的是,在括号中的变量赋值是没有意义的,因为子shell完成退出后,其中所有的变量都不会保留。

  • { list; } 命令组语句
       与()不同的是,该组命令会在当前shell环境中执行,而不是在新建的子shell环境中。 {}为保留字,所以其必须与命令用元符号(如空格)分开;()为操作符,其不必要与命令分开。

  • coprocess 语句

    1
    coproc [NAME] command [redirections]
  • 并发语句
      并发特性不是bash内部的特性,而是依靠外部工具来实现。GNU Parallel提供了这样的特性。parallel可用于替代xargs。

    1
    2
    find . -type f -name '*.html' -print | parallel gzip    
    parallel -j 10 < file
  • function 函数语句

    1
    fname () compound-command [ redirections ]
    1
    function fname [()] compound-command [ redirections ]

    ‘#’ 变量被用来存放参数的个数;‘0’ 变量存放第一个参数即函数名,后面的数字变量依次存放剩余的参数(大于10的参数应使用{})。local参数设置局部变量,变量的作用域是定义它的代码块以及被该代码块调用的子函数。unset可以取消变量定义。declare或者typeset列出当前环境所有变量名,-f参数只列出函数名。(参考SICP中的环境模型)

Shell参数

  变量赋值:name=[value] 注意:变量不但有值而且有0或多个属性。 赋值语句可以作为 declare、typeset、alias、export、readonly和local的参数。
+= 操作符 可以扩充变量。name+=[value],例如对PATH环境变量经常会如此操作。
名字引用declare -n ref=$1 出现对ref操作的任何地方都会实际操作$1。
位置参数: $N 或 ${N}(当N大于10时)
特殊参数
* : 被展开为参数列表,从1开始;
@ :被展开为参数列表,从1开始;
# :参数个数;
? :最近执行的前端pipeline的退出状态
$ :当前进程ID,在()子shell中,它展开为调用shell的ID,而不是子shell的ID。
! :最近放入后端执行的进程ID。
0 :shell或者shell脚本的名字。
_ :shell执行外部命令时的命令全名。

Shell展开

  参考 Shell参数展开-博客园

  • {}展开:mkdir /usr/local/src/bash/{old,new,dist,bugs}

  • ~展开:~:HOME; ~+ :PWD,~- :OLDPWD ; ~N:被替换为‘dirs +N’

  • 参数展开
    基本格式:${parameters}
    其他格式:

    1
    2
    3
    4
    ${parameter:-word}
    ${paramater:=word}
    ${paramater:?word}
    ${paramater:+word}

    索引子字符串展开: ${paramater:offset:length}

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    $ string=01234567890abcdefgh 
    $ echo ${string:7}
    7890abcdefgh
    $ echo ${string:7:2}
    78
    $ array[0]=01234567890abcdefgh
    $ echo ${array[0]:7}
    7890abcdefgh
    $ set -- 1 2 3 4 5 6 7 8 9 0 a b c d e f g h
    $ echo ${@:7}
    7 8 9 0 a b c d e f g h
    $ array=(0 1 2 3 4 5 6 7 8 9 0 a b c d e f g h)
    $ echo ${array[@]:7}
    7 8 9 0 a b c d e f g h

    模式匹配子字符串展开:
    ${变量#关键词} 若变量从头开始的数据匹配关键词,则将匹配的最 数据删除
    ${变量##关键词} 若变量从头开始的数据匹配关键词,则将匹配的最 数据删除
    ${变量%关键词} 若变量从尾向头开始的数据匹配关键词,则将匹配的最 数据删除
    ${变量%%关键词} 若变量从尾向头开始的数据匹配关键词,则将匹配的最 数据删除

    查找替换展开:
    ${变量/旧字符串/新字符串} 若变量内容匹配旧字符串,则第一个匹配的旧字符串会被新字符串取代。
    ${变量//旧字符串/新字符串} 若变量内容匹配旧字符串,则全部旧字符串会被新字符串取代。

  • 命令展开$(command)或者 "``command``"

  • 算术展开$(( expression ))

  • 进程替换<(list)或者>(list),(list)执行的结果会存到一个文件中,该文件然后被作为重定向的目标。

  • 字符分列:将IFS设置为分隔符号,参考 Bash拆分字符串

  • 文件名展开:模式匹配

  • Quote Removal

重定向

  • <<< 从字符串重定向

执行命令

  任何命令的执行都必须有上下文环境。环境中主要是key-value对,包括变量、打开文件描述符、重定向描述符等。内部命令、函数执行时不会生成子shell环境,而其他命令的执行会生成子shell环境。子shell环境继承了部分或者全部父环境的内容,在子shell环境中的操作不会影响父环境。
  退出状态:命令如果终止于一个信号N,退出状态会被shell设置为128+N。127表示命令没找到,126表示命令找到但没有执行,1表示一般性错误,2表示错误的shell命令。
  信号

内置命令

  • : [arguments] 参数扩展和重定向
  • . filename [arguments] 执行命令 基本上和source命令等价
  • break[n]cd continue [n]
  • eval [arguments] 参数被合并成命令,然后载入执行。
  • exec [-cl] [-a name] [command [arguments]] 如果支持command命令,则代替shell执行,而不是在创建的新的进程中执行,这与普通命令不同。
  • exit [n] 以状态码n退出。
  • export [-fn] [-p] [name[=value]] 标记name可以在子进程环境中可见。-f选项表示name是函数,否则name表示变量。
  • getopts optstring name [arg...]
  • hash [-r] [-p filename] [-dt] [name] 建立命令路径的hash映射,加快搜索效率。
  • pwd [-LP]return [n]
  • readonly [-aAf] [-p] [name[=value]] 标记name只读
  • shift [n] 对位置参数的编码向左移位n位
  • test expr[ expr 执行条件表达式expr;当使用[格式时,命令的最后一个参数必须是]。表达式可以使用操作符进行组合,这些操作符包括!()expr1 -a expr2(逻辑与)、expr1 -o expr2(逻辑或)
  • times
  • trap [-lp] [arg] [sigspec...] 绑定信号处理函数
  • umask [-p] [-S] mode 设置文件创建掩码
  • unset [-fnv] [name] 从环境中去除name符号
  • alias [-p] [name[=value] ...] 设置别名
  • bind [-m keymap] ... Readline绑定(bash专用)
  • builtin [shell-builtin [args]] 运行shell-builtin命令,在定义与内置命令有着相同名字的函数中,该命令可以重新获得原来内置命令的功能。
  • caller [expr] 返回当前激活的子程序调用的内容。
  • command [-pVv] command [arguments …] 执行非函数类命令
  • declare [-aAfFgiIlnrtux] [-p] [name[=value] …]声明变量和设置变量属性,例如只读、调试跟踪、名字引用、大小写转换、整数、数组。
  • echo [-neE] [arg ...]
  • enable [-a] [-dnps] [-f filename] [name ...]enable或者disable内置shell命令,使用-n选项代表disable。
  • help [-dms] [pattern]获取内置命令帮助信息
  • let expression [expression …]使表达式可以进行算术运算
  • local [option] name[=value] …
  • logout [n]
  • mapfile 从标准输入或者文件描述符生成索引数组
  • printf [-v var] format [arguments]格式化输出
  • read -ers] [-a aname] [-d delim] [-i text] [-n nchars] [-N nchars] [-p prompt] [-t timeout] [-u fd] [name …] 从标准输入或者文件描述符读入一行
  • readarray -d delim] [-n count] [-O origin] [-s count] [-t] [-u fd] [-C callback] [-c quantum] [array] 从标准输入或者文件描述符读入一行到索引数组
  • source filename
  • type [-afptP] [name …] 输出命令的类型
  • typeset 兼容Korn Shell,等价于declare
  • ulimit 设置资源限制
  • unalias [-a] [name … ] 去除别名
  • set
  • shopt

环境变量

  • CDPATHHOMEIFSMAILMAILPATHPATHPS1PS2BASH*BASH_*COMP_*EUIDHIST*(与历史记录有关)
  • PS1的设定:\d(日期)、\H(主机名)、\h(反取主机名在第一个小数点之前的名字)、\t(显示24小时格式时间)、\T\A\@(显示12小时格式的时间)、\u(当前用户名称)、\v(Bash版本号)、\w(完整工作目录名称)、\W(显示最后一个目录名)、\#(下达的第n个指令)、\$(提示字符)

Bash特性

条件表达式:

  • -a|-b|-c|-d|-e|-f|-g|-h|-k|-p|-r|-s|-t|-u|-w|-x|-G|-L|-N|-O|-S| file 文件判断单元操作符
  • file1 -ef file2 file2和file1是否引用相同的节点
  • file1 -nt file2 file1的修改时间更新或者file1存在,file2不存在。
  • file1 -ot file2 file1的修改时间更旧或者file2存在,file1不存在。
  • -v varname 变量varname是否被设置
  • -z string 字符串长度是否为0
  • -n stringstring 字符串长度是否非0
  • string1 == string2string1=string2string1 != string2
  • string1 > string2string1 < string2
  • arg1 Op arg2 : Op可以是 -eq-ne-lt-le-gt-ge算法比较
    算术:使用let或者(())或者declare -i
  • id++id--++id--id+ - ! ~ ** * / % >> << <= >= < > == != & ^ | && || expr1?expr2:expr3
    数组
  • name[subscript]=value 自动生成索引数组,subscript可以是数字或表达式
  • declare -a name
  • name=(value1 value2 … )
    1
    2
    3
    name="ds,fds,fd,wq,daw"
    IFS=","
    array=($name) # 自动将name根据','号进行分列。
    目录栈
  • dirs [-clpv] [+N | -N]
  • pushd [-n] [+N | -N | dir]
  • popd [-n] [+N | -N]
    提示符设置:
  • \a Bell、\d date、\h\H 主机名、请输入代码
    各种其他模式
  • rbash 严格模式
  • bash --posix posix模式
  • 兼容模式

作业控制

  作业控制与操作系统的进程控制有关。操作系统会引入终端组进程ID的概念,组ID为当前终端进程ID的组可以接收终端信号;而后端运行的组则会对信号免疫。Ctrl-z会导致当前前端运行的进程停止并将控制返回给bash;

  • bg [jobspec...]
  • fg [jobspec ...]
  • jobs [-lnprs] [jobspec] 或 jobs -x command [arguments]
  • kill
  • wait [-fn] [-p varname] [jobspec or pid …]
  • disown [-ar] [-h] [jobspec … | pid … ]
  • suspend [-f]

历史记录

  • fc

  • history

  • !!重复执行前个命令

  • !n 执行第n个命令