bash 学习笔记

注释:

  • 全文参考 bash 手册,阅读原文:man bash,或者在线 man page
  • 笔记只摘录个人感兴趣的内容,个人的理解放到注释下面
  • 注释文字中,控制台输入 ls -lls -l 是命令字符串,默认后面输入回车键,下文不再赘述
  • 如果想查看在命令行看中文手册,ubuntu 安装方式:sudo apt install manpages-zh
  • 唯一的问题就是中文的版本是2.05b 有些低,目前的版本是 5.1,当然新版本并没有增加太多内容
  • 感谢中文 man 翻译计划,github 仓库地址:https://github.com/man-pages-zh/manpages-zh

1. 名称

bash 是 Bourne-Again SHell 的缩写,即 GNU 命令解释程序 “Bourne 二代”。

2. 描述

Bash 是一个与 sh 兼容的命令解释程序,可以执行从标准输入或者文件中读取的命令。

注释:

  • 直接在控制台输入 sh,进入交互式的 sh
  • 输入 lsexit 两个命令,应该跟 bash 下的体验差不多
  • sh 是 Bourne Shell,Bourne 一代;而 bash 是 Bourne 二代
  • bash 是 shell,见上面的名称章节,shell 翻译为外壳
  • shell 跟 kernel(内核)相对应,比喻内核外面的一层,即用户跟内核交互的界面
  • cat /etc/shells 命令可以查看当前的 Linux 系统安装的所有 Shell

3. 概述

用法:bash [长选项] [短选项] [命令字符串 | 脚本文件名]

注释:

  • 长选项要放到短选项之前,参考:bash --help,以及下面的选项章节

4. 选项

4.1. 长选项

--help 在标准输出显示用法信息并成功退出

--version 在标准输出显示此 bash 的版本信息并成功退出

注释:

  • 控制台输入 bash --help,查看 bash 的帮助信息
  • 控制台输入 bash --version,查看 bash 的版本信息
  • bash 手册也是有版本的,bash 手册的版本跟 bash 版本保持一致
  • bash 手册的版本在 man bash 最后一行
  • bash 中文手册的版本是 2.05b 比较低,现在 bash 的版本是 5.1
  • 基本常识,软件的版本号越大,功能越多,而且软件基本上向下兼容
  • 因此,低版本的 bash 功能是高版本 bash 的子集

4.2. 短选项

-c cmd_string 如果有 -c 选项,那么命令将从 cmd_string 中读取。如果 cmd_string 后面有参数,它们将用于给位置参数(位置参数以 $0 起始)赋值。

-i 如果有 -i 选项,shell 将交互地执行。

注释:

  • 不用 -i 选项,控制台输入 bash,默认就会进入交互式的 Shell
  • 所以 -i 选项的用处不大,关键是要知道 Shell 分为交互模式非交互模式
  • -c 选项,c 代表 command,-c string 是非交互式运行 string 中的命令
  • 控制台输入 bash -c "ls -l",查看当前目录下的内容,当然不如 ls -l 来的直接
  • 控制台输入 bash -c help 获取帮助信息,help 是 bash 内置命令,zsh 不支持 help 内置命令
  • node 和 python 作为脚本解释器,跟 bash 命令解释器的用法类似,下面是两个非交互模式
  • node -e "console.log('Hello, nodejs world!')",类似 bash -c "echo 'Hello, bash world!'
  • python3 -c "print('Hello, python world!')",类似 bash -c "echo 'Hello, bash world!'
  • 交互模式,就是对话模式,输入命令,输出命令执行的结果,输入和输出是一问一答,就是交互模式
  • 控制台输入 bash,进入交互模式后,输入 help 查看帮助信息,exit 退出交互模式
  • 控制台输入 bash,输入 echo "Hello, bash world!",输入 pwd,输入 ls,输入 exit
  • 非交互模式,就是批处理模式或脚本模式,输入脚本文件,输出脚本文件执行的结果
  • 这个手册即有交互式 Shell 的用法,也有非交互式 Shell 的用法,也就是 Shell 编程
  • 编写一个简单的脚本文件 hello.sh 内容如下:
    echo 'Hello, bash world!'
    echo 'I am wangding.'
    
  • 控制台输入 bash hello.sh,以非交互模式运行 hello.sh 脚本文件,bash 是脚本解释器
  • 如果有 nodejs 或 python 脚本编程的经验,bash 的两种运行方式很容易理解
  • node hello.js,以非交互模式运行 hello.js 脚本文件,可以做 bash hello.sh 类似的事情
  • python3 hello.py,以非交互模式运行 hello.py 脚本文件,可以做 bash hello.sh 类似的事情
  • nodepython3 进入交互模式,跟 bash 交互模式类似,用 exit 退出交互模式
  • bash 是鼻祖,nodejs 和 python 参考和借鉴 bash 是肯定的

-r 如果有 -r 选项,shell 成为受限的(参见下面的受限的 shell 章节)。

-s 如果有 -s 选项,或者如果选项处理完以后,没有参数剩余,那么命令将从标准输入读取。这个选项允许在启动一个交互 shell 时可以设置位置参数。

[-+]O [shopt_option] shopt_option 是一个 shopt 内置命令可接受的选项(参见下面的内置命令章节)。如果有 shopt_option,-O 将设置那个选项的取值;+O 取消它。如果没有给出 shopt_option,shopt 将在标准输出上打印设为允许的选项的名称和值。

-- -- 标志选项的结束,禁止其余的选项处理。任何 -- 之后的参数将作为文件名和参数对待。参数 - 与此等价。

注释:

  • -r 用处不大,知道有受限 Shell,了解一下受限 Shell 有哪些功能被阉割了就行
  • -s 用处不大,关于参数位置,-c string 选项中也提到了,执行下面的操作就明白了
    bash -s 1 2
    echo $0
    echo $1
    echo $2
    
  • 脚本文件执行时,也可以有命令行参数,编写包含上面三个 echo 命令的脚本文件 parameters.sh
  • 控制台输入 bash parameters.sh 1 2,查看输出结果,注意 $0 代表的是脚本文件本身
  • [-+]O [shopt_option] 这个选项可以设置或查看 bash 参数,不同的参数控制 bash 的不同行为
  • 控制台输入 bash -O,列出 bash 所有参数,并进入 bash 交互模式,on 为参数开启状态,off 为参数关闭状态
  • 以 interactive_comments 参数为例,interactive_comments on,这个参数默认为开启状态,允许命令行注释
  • 输入 #ls,控制台没有报错,# 后面的内容视为注释,bash 直接忽略,exit 退出 bash 交互模式
  • 关闭 interactive_comments 参数,控制台执行 bash +O interactive_comments,进入 bash 交互模式
  • 控制台输入 shopt 查看 bash 所有参数状态,看到 interactive_comments off
  • 控制台输入 #ls,提示 找不到命令 “#ls”,说明此时 bash 不支持控制台的 # 注释行为

5. 参数(arguments)

如果选项处理之后仍有参数剩余,并且没有指定 -c 或 -s 选项,第一个参数将假定为一个包含 shell 命令的文件的名字。

如果 bash 是以这种方式启动的,$0 将设置为这个文件的名字,位置参数将设置为剩余的其他参数。

Bash 从这个文件中读取并执行命令,然后退出。Bash 的退出状态是脚本中执行的最后一个命令的退出状态。如果没有执行命令,退出状态是 0。

尝试的步骤是先试图打开在当前目录中的这个文件,接下来,如果没有找到,shell 将搜索脚本的 PATH 环境变量中的路径。

注释:

  • 编写 parameters.sh 内容如下:
    echo $0
    echo $1
    echo $2
    
  • 控制台输入 bash parameters.sh 1 2,观察输出内容,$0 表示 parameters.sh,$1 是 1,$2 是 2

6. 启动

当 bash 是作为交互的登录 shell 启动的,或者是一个非交互的 shell 但是指定了 --login 选项,它首先读取并执行 /etc/profile 中的命令,只要那个文件存在。读取那个文件之后,它以如下的顺序查找 ~/.bash_profile~/.bash_login,和 ~/.profile,从存在并且可读的第一个文件中读取并执行其中的命令。--noprofile 选项可以用来在 shell 启动时阻止它这样做。

当一个登录 shell 退出时, bash 读取并执行文件 ~/.bash_logout 中的命令,只要它存在。

当一个交互的 shell 但不是登录 shell 启动时,bash 从文件 ~/.bashrc 中读取并执行命令,只要它存在。可以用 --norc 选项来阻止它这 样做。--rcfile file 选项将强制 bash 读取并执行文件 file 中的命令,而不是 ~/.bashrc 中的。

注释:

  • cat /etc/profile,查看 /et/profile 文件内容,暂时看不懂没关系,把这个文档都学完了,自然就懂了
  • cat ~/.profile,查看 ~/.profile 文件内容
  • cat ~/.bash_logout,查看 ~/.bash_logout 文件内容
  • 打造酷炫的登录欢迎信息,sudo apt install screenfetch
  • 修改 .bashrc 文件,在其中添加 screenfetch 2>/dev/null
  • 控制台输入 bash,进入 bash 交互模式,看到 screenfetch 输出,有 Linux 发行版的 Logo 及系统信息
  • 另一种可选方案是使用 fortune,登录后显示名言和古诗词

7. 定义

下列定义在文档余下部分中通用。

blank 空白

一个空格或是 tab

word

一个字符序列,shell 将它们视为一个结构单元,也称为一个 token 片段。

name 名称

一个只由字母,数字和下划线构成的词,并且以字符或下划线起始,也称为一个标识符。

metacharacter 元字符

一个字符,如果不是引用的话,将成为词的分隔符。它是这些字符之一:| & ; ( ) < > space tab newline

control operator 控制操作符

一个标识,拥有控制功能。它是这些符号之一:|| & && ; ;; ;& ;;& ( ) | |& <newline>

注释:

  • 这些术语是下面描述 Shell 编程语法时会用到的,很多符号都有多重用途
  • 如果有 C 语言或者其他高级语言的编程经验,部分元字符和控制操作符的作用跟编程语言是类似的
  • blank 的作用:Bash 使用空格(或 Tab 键)区分不同的参数,参数之间多个空格都视为一个空格
  • name 相当于编程语言中的变量名和函数名,即标识符
  • metacharacter 如果不是引用,将成为词的分隔符,下面是每个符号的用法
  • | 是管道操作符,它可以把多个简单命令连接组合成功能复杂的管道命令
  • 管道操作符是个非常强大的功能,手册后面还会详细介绍
  • & 用途很多,放到命令后面时,命令被放到后台运行,也可以用在重定向,后面会详细介绍
  • ; 类似 C 语言中语句的分隔符,是命令的结束符,使得一行可以放置多个命令
  • 上一个命令执行结束后,再执行下一个命令,控制台输入:pwd; ls -l,体验一下
  • 分号也是 for 循环中,循环变量初始化、递增和退出条件的分隔符
  • () 类似 C 语言中的 if、switch、 while 等条件表达式的用法
  • < > 用于重定向,或在条件表达式中表示小于和大于
  • control operator 控制操作符,拥有控制功能
  • || 类似 C 语言中逻辑或运算,而 && 类似 C 语言中的逻辑与运算
  • 这两个控制符会控制多个命令的执行方式
  • 控制台逐行输入下面的命令,体验一下,假设 abc 文件不存在,则 cat abc 运行失败
    cat abc; pwd
    cat abc && pwd
    cat abc || pwd
    
  • ;; 用在 case 分支语句块中,代表分支语句块的结束

8. 保留字

保留字是对 shell 有特殊意义的词。下列词被识别为保留的,如果不是引用,并且不是一个简单命令的起始词(参见下面的Shell 语法),也不是 case 或者 for 命令的第三个词:

! case coproc do done elif else esac fi for function if in select then until while { } time [[ ]]

注释:

  • 学过编程的人都知道,保留字就是编程语言中的关键字,保留字不允许用作变量名或函数名
  • 大部分保留字跟 C 语言中的关键字用法相同

9. shell 语法

注释:

  • 这个章节的内容尤其是复合命令,涉及 Shell 非交互模式,即 Shell 编程的内容
  • shell 语法中的简单命令、管道命令、列表命令和复合命令
  • 类似编程语言中的变量和运算符、表达式、语句和语句块之间的层次关系
  • 多个简单命令通过管道操作符可以连接成管道命令
  • 多个简单命令或者多个管道命令又组成列表命令
  • 多个列表命令又组成复合命令
  • 从编程语言的语法角度来理解这些概念,会简单和容易很多
  • 站在编程语言的语法角度,shell 语法不是什么新鲜玩意

9.1. 简单命令

简单命令是(可选的)一系列变量赋值,紧接着是 blank 空白分隔的词和重定向,然后以一个控制操作符结束。

第一个词指明了要执行的命令,它被作为第 0 个参数。其余词被作为这个命令的参数。

简单命令的返回值是它的退出状态,或是 128+n,如果命令被信号 n 结束的话。

注释:

  • 说人话,简单命令就是一个命令,格式为:command [options] [arguments]
  • 命令字符串是第 0 个参数,即 $0,后续的选项和参数用空格分割
  • 关于退出码,正常退出是 0。异常退出,在控制台依次输入下面的命令:
    z note
    gitbook serve --port 8080
    ctrl + c
    echo $?
    
  • 退出码是 130,ctrl + c 的信号是 SIGINT 信号的值 n 是 2,所以退出码是 128+2 = 130
  • 查看信号的资料,输入命令:man 7 signal

9.2. 管道命令

管道是一个或多个命令的序列,用字符 | 分隔。管道的格式是这样:

[time [-p]] [ ! ] command [ | command2 ... ]

命令 command 的标准输出通过管道连接到命令 command2 的标准输入。连接是在命令指定的任何重定向之前进行的。

如果保留字 ! 作为管道前缀,管道的退出状态将是最后一个命令的退出状态的逻辑非值。否则,管道的退出状态就是最后一个命令的。

如果保留字 time 作为管道前缀,管道中止后将给出执行管道耗费的用户和系统时间。

注释:

  • time 用来做基准测试,返回命令运行的时长,单位是秒
  • zsh 的 time 输出结果,格式有些混乱
  • 通过 time 可以很方便的做基准测试,例如:比较 nodejs 和 python 解释器的速度
  • 在控制台输入 bash,进入交互模式后,在控制台输入下面两个命令
    time node -e "console.log('helo')"
    time python3 -c "print('hello')"
    
  • 基准测试结果,应该是 python 解释器的速度更快
  • 注意 time 不是内置命令,应该是 Bash 保留关键字,控制台输入:which time 验证一下
  • 类似的内置命令是 times,控制台输入:which times 验证一下
  • /usr/bin/time 同样是做基准测试的工具,功能要更强大一些,控制台输入下面两个命令
    /usr/bin/time -v node -e "console.log('hello')"
    /usr/bin/time -v python3 -c "print('hello')"
    
  • ! 是逻辑非值,跟 C 语言中的取反逻辑运算相同
  • 在控制台输入 lsecho $?
  • 在控制台输入 ! lsecho $?
  • unix 的哲学是小而美,每个命令的功能单一
  • 通过管道操作把多个简单命令连接起来,完成一个复杂功能
  • 例如:下面的管道命令,用来计算某个目录下 JavaScript 文件的代码行数
  • find . -name '*.js' ! -path "./**/node_modules/*" ! -path "./node_modules/*" | xargs cat | grep -v ^$ | wc -l
  • 现代化图形界面的 IDE,很少提供类似的代码行数统计功能,可见 bash 的强大,以及顽强的生命力
  • 例如:完成文本行的排序和去重,控制台输入:cat data.txt | sort | uniq

9.3. list 命令

list 命令是一个或多个管道。用操作符 ;,&,&&,或 ⎪⎪ 分隔的序列,并且可以选择用 ;,&,或新行符结束。

这些 list 操作符中,&& 和 ⎪⎪ 优先级相同,其次是 ; 和 &,它们的优先级是相同的。

list 中可以有一个或多个新行符来分隔命令,而不是使用分号分隔。

如果一个命令是由控制操作符 & 结束的,shell 将在后台的子 shell 中执行这个命令。shell 不会等待命令执行结束,返回状态总是 0。以分号 ; 分隔的命令会被顺序执行;shell 会等待每个命令依次结束。返回状态是最后执行的命令的返回状态。

控制操作符 && 和 ⎪⎪ 分别代表 AND 和 OR 序列。

一个 AND 序列的形式是:command1 && command2,command2 只有在 command1 返回 0 时才被执行。

一个 OR 序列的形式是:command1 ⎪⎪ command2,command2 只有在 command1 返回非 0 时才被执行。

AND 和 OR 序列的返回状态是 list 中最后执行的命令的返回状态。

注释:

  • 三种逻辑运算:取反 !,与 &&,或 ||,还有分号的语句结尾,Shell 语法跟 C 语法如出一辙
  • 结合下面的复合命令描述,list 命令其实就是命令组成的语句块
  • 控制台执行两个列表命令:pwd; echo '-------'; ls;pwd; ls | sort;
  • 根据手册描述,上面两个 list 命令,最好用新行来隔开两个命令
  • 这两个 list 命令,之所以没有这样做,是因为用新行,就成交互模式了
  • 但是放到脚本里面是没有问题,例如:脚本文件 list.sh 内容如下:
    pwd
    echo '--------------'
    ls -l
    
  • 控制台输入:bash list.sh
  • & 符号放到命令末尾,这个命令将在后台运行,注意是否为挂起状态,挂起的进程不会执行
  • vim & vim 在后台运行,是挂起状态,fg 可以把 vim 调到前台
  • 关于 && 和 || 两种列表命令的执行方式,执行以下的操作来体会一下
  • 控制台输入:pwd && ls! pwd && ls 观察两者的区别
  • 控制台输入:pwd || ls! pwd || ls 观察两者的区别

9.4. 复合命令

复合命令是如下情况之一:

(list)

list 命令将在一个子 shell 中执行。变量赋值和影响 shell 环境变量的内建命令在命令结束后不会再起作用。返回值是序列的返回值。

{ list; }

list 命令将在当前 shell 环境中执行。序列必须以一个新行符或分号结束。这种做法也称为命令组。返回值是序列的返回值。注意与元字符 ( 和 ) 不同, { 和 } 是保留字,必须出现在能够识别保留字的场合。由于它们不会产生断词,它们和序列之间必须用空格分开。

((expression))

表达式 expression 将被求值。求值规则在下面的算术求值章节中描述。如果表达式的值非零,返回值就是 0;否则返回值是 1。这种做法和 let "expression" 等价。

[[ expression ]]

返回 0 或 1,取决于条件表达式 expression 求值的情况。表达式是由下面条件表达式章节中描述的原语组成。[[ 和 ]] 中的词不会进行词的拆分和路径的扩展处理;而波浪线扩展,参数和变量扩展,算术扩展,命令替换,函数替换和引用的去除则都将进行。

当使用 == 和 != 操作符时,操作符右边的字符串被认为是一个模式,根据下面模式匹配章节中的规则进行匹配。如果匹配则返回值是 0,否则返回 1。模式的任何部分可以被引用,强制使它作为一个字符串而被匹配。

表达式可以用下列操作符结合起来。根据优先级的降序列出:

  • ( expression ) 返回表达式 expression 的值。括号可以用来提升操作符的优先级
  • ! expression 返回真,前提是表达式 expression 返回假
  • expression1 && expression 返回真,前提是表达式 expression1 和 expression2 都返回真。
  • expression1 || expression2 返回真,前提是表达式 expression1 或者 expression2 二者之一返回真。

&& 和 || 操作符不会对表达式 expression2 求值,前提是 expression1 可以决定整个条件表达式的返回值。

for name [ in word ] ; do list ; done

in 之后的一系列词会被扩展,产生一个项目列表。变量 name 被依次赋以这个列表中的每个元素,序列 list 每次都被执行。如果 in word 被忽略,那么 for 命令遍历已设置的位置参数(参见下面的参数章节),为每一个执行一次序列 list。返回值是最后一个命令的返回值。如果 in 之后的词扩展的结果是空列表,就不会执行任何命令,返回值是 0。

for (( expr1 ; expr2 ; expr3 )) ; do list ; done

首先,算术表达式 expr1 被根据下面算术求值中的规则进行求值。然后算术表达式 expr2 被循环求值,直到它等于 0。每次 expr2 结果非零时,序列 list 都被执行,算术表达式 expr3 被求值。如果任何表达式被忽略,将被视为执行结果是 1。返回值是序列 list 中被执行的最后一个命令的返回值;或者是 false,如果任何表达式非法的话。

select name [ in word ] ; do list ; done

in 之后的一系列词会被扩展,产生一个项目列表。这个扩展后的词集合被输出到标准错误上,每个前面加上一个数字。如果 in word 被忽略,将输出位置参数(参见下面的参数章节)。PS3 提示符将被显示出来,等待从标准输入得到一行输入。如果输入是一个数字且显示中有对应的词,那么变量 name 的值将设置为这个词。如果输入一个空行,那么词和提示符将再次显示出来。如果读入了一个 EOF,命令就结束。任何其他值将设置变量 name 为空。读入的行保存为变量 REPLY,序列 list 在每次选择之后都会执行,直到执行了一个 break 命令。select 的退出状态是序列 list 中执行的最后一个命令的退出状态,如果没有执行命令就是 0。

case word in [ [(] pattern [ | pattern ] ... ) list ;; ] ... esac

case 命令首先扩展 word,然后依次试着用每个 pattern 来匹配它,使用与路径扩展相同的匹配规则(参见下面的路径扩展章节)。如果找到一个匹配,相应的序列将被执行。找到一个匹配之后,不会再尝试其后的匹配。如果没有模式可以匹配,返回值是 0。否则,返回序列中最后执行的命令的返回值。

if list; then list; [ elif list; then list; ] ... [ else list; ] fi

序列 if list 被执行。如果退出状态是 0,then list 将被执行。否则,每个 elif 将被一次执行,如果退出状态是 0,相应的 then list 将被执行,命令结束。否则,else list 将被执行,如果存在的话。退出状态是最后执行的命令的退出状态,或者是 0,如果所有条件都不满足。

while list; do list; done until list; do list; done

while 命令不断地执行序列 do list,直到序列中最后一个命令返回 0。until 命令和 while 命令等价,除了对条件的测试恰好相反;序列 do list 执行直到序列中最后一个命令返回非零状态值。while 和 until 命令的退出状态是序列 do list 中最后一个命令的退出状态,或者是 0,如果没有执行任何命令。

[ function ] name () { list; }

这样可以定义一个名为 name 的函数。函数体 body 是包含在 { 和 } 之间的命令序列 list。在指定将 name 作为一个命令运行的场合,这个序列将被执行。函数的退出状态是函数体最后执行的命令的退出状态。

注释:

  • 如果有编程基础,复合命令没什么难度,主要就是分支和循环两种流程控制
  • 在控制台逐行输入下面的命令,体会一下

    age=25; if(($age>20)); then echo 'You are a man.'; else echo 'You are a boy.'; fi
    for name in /*; do echo $name; done
    for((num=0; num<5; num++)); do echo wangding; done
    select name in /*; do echo $name; done
    
  • 剩余大部分的示例,以脚本文件的方式运行,bash file.sh

  • for.sh

    for num in {1..5} ;
    do
    echo "$num  wangding"
    done
    
    echo '------------'
    
    for num in {5..1} ;
    do
    echo "$num  wangding"
    done
    
  • for-in-step.sh

    for num in {1..9..2} ;
    do
    echo "$num  wangding"
    done
    
  • for-string.sh

    str="My name is wangding and am 20 years old.";
    
    for word in $str;
    do
    echo $word
    done
    
  • for-break.sh

    num=1
    for((; ;));
    do
    echo "$num wangding"
    ((num++))
    if(( $num > 5 )); then
      break;
    fi
    done
    
  • while.sh

    num=0
    while [ $num -lt 10 ];
    do
    echo 'wangding'
    ((num++))
    done
    
  • while-c-style.sh

    num=0
    while (($num < 10));
    do
    echo 'wangding'
    ((num++))
    done
    
  • while-break.sh

    num=1
    while :
    do
    echo "$num: wangding"
    ((num++))
    if [ $num -gt 5 ];
    then
      break
    fi
    done
    
  • while-continue.sh

    num=0
    while [ $num -lt 10 ];
    do
    ((num++))
    if [ $((num%2)) -eq 0 ];
    then
      continue
    fi
    echo "$num: wangding"
    done
    
  • while-case-opt.sh

    echo 'bash while-case-opt.sh -n wangding -a 20'
    
    while getopts n:a: OPT
    do
    case "${OPT}"
    in
      n) name=${OPTARG};;
      a) age=${OPTARG};;
      *) echo "Invalid option"
    exit 1;;
    esac
    done
    
    printf "My name is $name and am $age years old.\n"
    
  • while-operate-file.sh

    while read a b c
    do
    echo $b - $a
    done < languages.txt
    while-read-file.sh
    num=1
    while read -r line;
    do
    echo "$num:  $line"
    ((num++))
    done < /etc/passwd
    
  • languages.txt

    Language    type      released
    Python      general   1991
    Javascript  web       1995
    Java        mobiLe    1995
    Rust        embebded  2010
    Go          backend   2007
    
  • case.sh

    scores=(95 81 88 56 34 71)
    
    for s in ${scores[@]};
    do
    case $(($s/10)) in
      9) echo "$s 优";;
      8) echo "$s 良";;
      7) echo "$s 中";;
      6) echo "$s 及格";;
      *) echo "$s 不及格";;
    esac
    done
    
  • function.sh

    factorial() {
    if [ $# -eq 0 ]; then
      return 0
    fi
    
    local fac=1
    for((num=1; num<=$1; num++));
    do
      ((fac*=$num))
    done
    return $fac
    }
    
    n=5
    factorial $n
    echo "$n! = $?"
    

10. 注释

# 开头行都被忽略,通常用在非交互模式。交互模式下,使用内建命令 shopt 启用了 interactive_comments 选项,# 也起注释作用。

注释:

  • 经常用在非交互模式,在 Shell 编程中,用来对脚本程序编写代码注释,帮助他人理解脚本程序
  • 交互模式下 interactive_comments 选项默认是启用的,所以交互模式也可以使用注释
  • 不通读 bash 手册的人,很少有人知道这个控制台小技巧,zsh 的交互模式也是启用的
  • 这个 bash 黑科技的关键是注释内容会放到历史命令里
  • 在控制台执行下面的操作,体会一下
  • #ls, history 会看到 #ls 命令, ctrl+r ls 会调出历史命令
  • 下面是这个技巧的使用场景
  • 假设我们要执行某个命令 c1,命令敲了一半,发现需要先执行另一个命令 c2,c1 回头再执行
  • 如果不知道这个技巧,我们会敲快捷键 ctrl + u,把控制台当前的 c1 命令删除,再输入 c2 命令
  • c2 完成后,再把 c1 命令重新敲一遍,完成最终的操作
  • 知道这个技巧后,不需要 ctrl + u 删除 c1 命令,而是在 c1 命令行首添加 # 字符,敲回车
  • 这个时候注释的 c1 命令会到放到命令历史中,直接敲 c2 命令,c2 执行完后
  • 从命令历史中调出来注释掉的 c1 命令,去掉最前面的注释符号,来运行 c1 命令
  • 因为是从命令历史中调出的 c1 命令,避免了从头输入 c1 命令,提高了工作效率

11. 引用

引用用来去掉特定字符或词的特殊意义。引用可以用来禁止对特殊字符的处理,阻止保留字被识别,还用来阻止参数的扩展。

上面在定义中列出的每个元字符对于 shell 都有特殊意义。如果要表达它的本义,必须引用它。

在使用命令历史扩展功能时(见下面的历史扩展),历史扩展字符,通常是 !,必须被引用,才会阻止历史扩展。

有三种引用机制:

  • 转义字符
  • 单引号
  • 双引号

一个未被引用的反斜杠(\)是转义字符。它保留其后一字符的字面意义,除非那是一个新行符。如果 \ 和新行符成对出现,并且反斜杠自身没有被引用,那么 \<newline> 被视为续行标志(意思是,它从输入流中删除,并被忽略了)。

将字符放在单引号之中,将保留引用中所有字符的字面意义。单引号不能包含在单引号引用之中,即使前面加上了反斜杠。

将字符放在双引号中,同样保留所有字符的字面意义,例外的情况是 $, `, 和 \。 字符 $ 和 ` 在双引号中仍然具有特殊意义。反斜杠只有后面是下列字符时才有特殊意义: $, `, ", \, 或 <newline>。双引号可以包含在双引号引用中,但要在前面加上一个反斜杠。

特殊的参数 *@ 在双引号中有特殊意义(参见下面的参数章节)。

形式为 $'string' 的词会被特殊处理。它被扩展为 string,其中的反斜杠转义字符被替换为 ANSI C 标准中规定的字符。反斜杠转义序列,如果存在的话,将做如下转换:

  • \a alert (bell) 响铃
  • \b backspace 回退
  • \e
  • \E an escape character 字符 Esc
  • \f form feed 进纸
  • \n new line 新行符
  • \r carriage return 回车
  • \t horizontal tab 水平跳格
  • \v vertical tab 竖直跳格
  • \\ backslash 反斜杠
  • \' single quote 单引号
  • \" double quote 双引号
  • \? question mark 问号
  • \nnn 一个 ASCII 字符,它的值是八进制值 nnn(一到三个八进制数)
  • \xHH 一个 ASCII 字符,它的值是十六进制值 HH(一到两个十六进制数)
  • \uHHH 一个 Unicode 字符,它的值是十六进制值 HHH(一到三个十六进制数)
  • \UHHHHHHHH 一个 Unicode 字符,它的值是八进制值 HHHHHHHH(一到八个八进制数)
  • \cx 一个 ctrl-x 字符

扩展结果是单引号引用的,就好像 $ 符号不存在一样。

双引号引用字符串前面加上一个 $ 符号将使得这个字符串被根据当前语言环境来翻译。如果当前语言环境是 C 或者 POSIX,这个符号将被忽略。 如果这个字符串被翻译并替换了,那么替换结果是双引号引用的。

注释:

  • 三种引用机制:转义字符(\),单引号(')和双引号("
  • 转义字符的作用,输出特殊字符,格式:$'string',支持的转换,见上面的列表
  • 在控制台逐行输入下列命令,查看转义效果
    echo $'hello\nworld'
    echo $'hello\tworld'
    echo $'hello\aworld'
    echo $'hello\bworld'
    echo $'\150\145\154\154\157\40\167\157\162\154\144'
    echo $'\x68\x65\x6c\x6c\x6f\x20\x77\x6f\x72\x6c\x64'
    echo $'\u68\u65\u6c\u6c\u6f\u20\u738b\u9876'
    
  • cat parameters.sh | tr $'\n' ';'tr 命令将 parameters.sh 脚本文件中的换行符替换成了 ;
  • 转义字符有一特殊用途,就是命令行续行
  • 一行命令太长写不下,可以用转义字符加换行符,另起一行,bash 会视为一行
  • 在控制台分别输入两个 echo 命令,体验一下
    echo Hello, bash world!
    echo Hel\
    lo, ba\
    sh world!
    
  • 除了转义字符,单引号和双引号才是真正的引用
  • 引用的作用,是消除元字符和控制符的特殊含义,得到该字符的本体
  • 单引号比双引号消除特殊含义的效果更强
  • 也就是说有些字符在双引号中消除不了特殊含义
  • 控制台逐行输入下面的命令,比较单引号和双引号的效果
    echo '$(pwd)'
    echo "$(pwd)"
    echo '`pwd`'
    echo "`pwd`"
    
  • $(pwd) 和 `pwd` 的作用等价,都是命令替换,即,得到 pwd 命令的输出
  • 单引号消除了命令替换,双引号没有消除命令替换
  • 单引号消除了 $() 和 ` 字符的特殊含义,双引号没有消除

12. 参数(parameters)

一个参数是一个储存值的实体。它可以是一个名称 name,一个数字或者是下面特殊参数章节中列出的特殊字符之一。从 shell 的角度来看,一个变量是一个由名称 name 代表的参数。一个变量有一个值 value 以及零个或多个属性。属性可以使用内建命令 declare 来设置(参见下面内置命令章节中对 declare 的描述)。

如果给一个参数赋值,那么它就被定义了。空字符串是有效的值。一旦一个变量被定义了,它只能用内建命令 unset 来取消(参见下面 shell 内置命令章节)。

一个变量可以用这样的语句形式来赋值:name=[value]

如果没有给出值,变量就被赋为空字符串。所有值都经过了波浪线扩展,参数和变量扩展,命令替换,算术扩展和引用的删除(参见下面的扩展章节)。如果变量设置了整数属性,那么值将进行算术扩展,即使没有应用 $((...)) 扩展(参见下面的算术扩展章节)。不会进行词的拆分,除非是下面特殊参数中提到的 "$@"。不会进行路径的扩展。赋值语句也出现在下列内建命令中,作为它们的参数:declare, typeset, export, readonly, 和 local

12.1. 位置参数

位置参数是以一或多个数字代表的参数,除了 0。位置参数是在 shell 启动时,根据它的参数来赋值的,也可以用内建命令 set 来重新赋值。位置参数不能用赋值语句来赋值。在一个 shell 函数被执行的时候,位置参数会被暂时地替换掉(参见下面的函数章节)。

当位置参数由两个以上的数字构成时,它必须放在括号内(参见下面的扩展章节)。

12.2. 特殊参数

shell 对一些参数做特殊处理。这些参数只能被引用而不能被赋值。

  • * 扩展为位置参数,从 1 开始。如果扩展发生在双引号中,它扩展为一个词,值是各个参数,以特殊变量 IFS 的第一个字符分隔。也就是说,"$*" 等价于 "$1c$2c...",这里 c 是变量 IFS 的第一个字符。如果没有设置 IFS,那么参数将用空格分隔。
  • @ 扩展为位置参数,从 1 开始。如果扩展发生在双引号中,每个参数都将扩展为一个词。也就是说,"$@" 等价于 "$1" "$2" ... 如果位置参数不存在,"$@" 和 $@ 扩展为空(即,它们被删除了)。
  • # 扩展为位置参数的个数,以十进制表示
  • ? 扩展为最近执行的前台管道的状态
  • - 扩展为当前选项标志。标志是在启动时或以内建命令 set 指定的,或者是 shell 自身设置的(例如选项 -i
  • $ 扩展为 shell 的进程 ID。在一个 () 子 shell 中,它扩展为当前 shell 的 进程 ID 而不是子 shell 的
  • ! 扩展为最近一次执行的后台(异步)命令的进程号
  • 0 扩展为 shell 或者 shell 脚本的名称。这个变量是在 shell 初始化时设置的。如果 bash 是执行脚本文件时启动的,$0 将设置为那个文件的名称。如果 bash 启动时的参数包含 -c,那么 $0 被设置为启动命令行被执行后的第一个参数,如果有的话。否则,它被设置为用来启动 bash 的文件名,就是参数 0。
  • _ shell 启动时,设置为 shell 或参数中被执行的 shell 脚本的绝对路径名。然后,在扩展时扩展为上一个命令的最后一个参数。它也被设置为被执行的每个命令的文件全名并且被设置到这个命令执行的环境当中。当检查邮件时,这个参数保存着正在检查的邮件文件的名称。

12.3. Shell 内置变量

shell 定义了下列变量:

  • BASH 扩展为用来启动当前 bash 实例的文件全名。
  • BASH_VERSINFO 一个只读数组变量,成员保存着当前 bash 实例的版本信息。赋予数组元素的值是如下这些:

    • BASH_VERSINFO[0] 主版本号
    • BASH_VERSINFO[1] 次版本号
    • BASH_VERSINFO[2] 补丁版本
    • BASH_VERSINFO[3] 编译信息
    • BASH_VERSINFO[4] 发布时的状态 (例如, beta1)
    • BASH_VERSINFO[5] MACHTYPE 平台类型
  • BASH_VERSION 扩展为一个字符串,描述了这个 bash 实例的版本

  • COMP_CWORD ${COMP_WORDS} 的索引,指向当前光标位置所在的词。这个变量只有在被可编程补全功能(参见下面的可编程补全章节)调用的 shell 函数中才可用。
  • COMP_LINE 当前命令行。这个变量只有在被命令补全功能调用的 shell 函数和外部命令中才可用
  • COMP_POINT 相对于当前命令起始处的当前光标位置。如果当前光标位置是当前命令的末端,它的值就和 ${#COMP_LINE} 相等。这个变量只有在被命令补全功能调用的 shell 函数和外部命令中才可用。
  • COMP_WORDS 一个数组变量(参见下面的数组章节),由当前命令行的各个单词构成。这个变量只有在被命令补全功能调用的 shell 函数中才可用
  • DIRSTACK 一个数组变量,包含当前目录栈的内容。栈中的目录排列的顺序就是用内建命令 dirs 显示时的顺序。对这个数组变量的成员赋值可以用来修改栈中已有的目录,但是要添加和删除目录就必须使用内建命令 pushdpopd。对它赋值不会改变当前目录。如果取消了 DIRSTACK 的定义,它就失去了它的特殊意义,即使后来重新定义它。
  • EUID 扩展为当前用户的有效用户 ID。它在 shell 启动时设置。它是只读的
  • FUNCNAME 当前执行的 shell 函数名。这个变量只有在执行一个 shell 函数时存在。向 FUNCNAME 赋值没有效果并且返回一个错误。如果取消了 FUNCNAME 的定义,它就失去了特殊的意义,即使后来重新定义它
  • GROUPS 一个数组变量,包含当前用户所属的组的列表。向 GROUPS 赋值没有效果并且返回一个错误。如果取消了 GROUPS 的定义,它就失去了特殊的意义,即使后来重新定义它
  • HISTCMD 当前命令的历史编号,或者历史列表中的索引。如果取消了 HISTCMD 的定义,它就失去了特殊的意义,即使后来重新定义它
  • HOSTNAME 自动设置为当前的主机名
  • HOSTTYPE 自动设置为一个字符串,唯一地标识着正在运行 bash 的机器类型。默认值是系统相关的
  • LINENO 每次引用这个参数时,shell 将它替换为一个指示在脚本或函数中当前行号的十进制数字(从 1 开始)。 如果不是在脚本或函数中,替换得到的值不一定有意义。如果取消了 LINENO 的定义,它就失去了特殊的意义,即使后来重新定义它
  • MACHTYPE 自动设置为一个字符串,完整的描述了正在运行 bash 的系统类型,格式是标准的 GNU cpu-company-system 格式。默认值是系统相关的
  • OLDPWD 上一次命令 cd 设置的工作目录
  • OPTARG 内置命令 getopts 处理的最后一个选项参数值(参见下面的内置命令章节)
  • OPTIND 内置命令 getopts 将处理的下一个参数的索引(参见下面的内置命令章节)
  • OSTYPE 自动设置的一个字符串,描述了正在运行 bash 的操作系统。默认值是系统相关的
  • PIPESTATUS 一个数组变量(参见下面的数组章节),包含最近执行的前台管道中的进程(可能只包含一个命令)的退出状态
  • PPID shell 的父进程的进程号。这个变量是只读的
  • PWDcd 命令设置的当前工作目录
  • RANDOM 每次引用这个参数时,都会产生一个 0 到 32767 之间的随机整数。可以通过向 RANDOM 赋值来初始化随机数序列。如果取消了 RANDOM 的定义,它就失去了特殊的意义,即使后来重新定义它
  • REPLY 变量的值将作为内建命令 read 的输入,如果命令没有参数的话
  • SECONDS 每次引用这个参数时,返回 shell 自运行以来的秒数。如果向 SECONDS 赋值,此后对它的引用将返回自赋值时起的秒数加上所赋予的值。如果取消 SECONDS 的定义,它就失去了特殊的意义,即使后来重新定义它。
  • SHELLOPTS 一个冒号分隔的被允许的 shell 选项列表。列表中每个词都是内置命令 set-o 选项的有效参数。SHELLOPTS 中出现的选项也是 set -o 显示为 on 的选项。如果 bash 启动时从环境中找到这个变量,那么在读取任何配置文件之前,列表中的每个选项都将被设置。这个变量是只读的。
  • SHLVL 每次启动一个 bash 的实例时都会增加
  • UID 扩展为当前用户的 ID,在启动时初始化。这个变量是只读的

下列变量被 shell 使用。有时 bash 会为变量赋默认值;这些情况在下面会标出。

  • BASH_ENV 如果 bash 在执行一个 shell 脚本时设定了这个变量,它的值将被解释为一个文件名,包含着初始化 shell 用到的命令,就像 ~/.bashrc 中一样。BASH_ENV 的值在被解释为一个文件名之前要经过参数扩展,命令替换和算术扩展。不会使用 PATH 来查找结果文件名。
  • CDPATH 命令 cd 的搜索路径。这是一个冒号分隔的目录列表,shell 从中查找 cd 命令的目标目录。可以是这样:".:~:/usr"
  • COLUMNS 用在内置命令 select 当中,用来判断输出选择列表时的终端宽度。 自动根据 SIGWINCH 信号来设置
  • COMPREPLY 一个数组变量,bash 从中读取可能的命令补全。 它是由命令补全功能调用的 shell 函数产生的
  • FCEDIT 内置命令 fc 默认的编辑器
  • FIGNORE 一个冒号分隔的后缀名列表,在进行文件名补全时被忽略(参见下面的逐行读取章节)。一个后缀满足其中之一的文件名被排除在匹配的文件名之外。可以是这样:".o:~"
  • GLOBIGNORE 一个冒号分隔的模式列表,定义了路径名扩展时要忽略的文件名集合。如果一个文件名与路径扩展模式匹配,同时匹配 GLOBIGNORE 中的一个模式时,它被从匹配列表中删除。
  • HISTCONTROL 如果设置为 ignorespace,以 space 开头的行将不会插入到历史列表中。如果设置为 ignoredups,匹配上一次历史记录的行将不会插入。设置为 ignoreboth 会结合这两种选项。如果没有定义,或者设置为其他值,所有解释器读取的行都将存入历史列表,但还要经过 HISTIGNORE 处理。这个变量的作用可以被 HISTIGNORE 替代。多行的组合命令的第二和其余行都不会被检测,不管 HISTCONTROL 是什么,都会加入到历史中。
  • HISTFILE 保存命令历史的文件名(参见下面的历史章节)。默认值是 ~/.bash_history。如果取消定义,在交互式 shell 退出时 命令历史将不会保存
  • HISTFILESIZE 历史文件中包含的最大行数。当为这个变量赋值时,如果需要的话,历史文件将被截断来容纳不超过这个值的行。默认值是 500。历史文件在交互式 shell 退出时 也会被截断到这个值
  • HISTIGNORE 一个冒号分隔的模式列表,用来判断那个命令行应当保存在历史列表中。每个模式都定位于行首,必须匹配整行(没有假定添加 *)。在 HISTCONTROL 指定的测试结束后,这里的每个模式都要被测试。除了平常的 shell 模式匹配字符,& 匹配上一个历史行。& 可以使用反斜杠来转义;反斜杠在尝试匹配之前将被删除。多行的组合命令的第二行以及后续行都不会被测试,不管 HISTIGNORE 是什么,都将加入到历史中。
  • HISTSIZE 命令历史中保存的历史数量(参见下面的历史章节)。默认值是 500
  • HOME 当前用户的个人目录;内建命令 cd 的默认参数。在执行波浪线扩展时也用到这个变量
  • HOSTFILE 包含一个格式和 /etc/hosts 相同的文件名,当 shell 需要补全主机名时要读取它。shell 运行过程中可以改变可能的主机名补全列表;改变之后下一次需要主机名补全时 bash 会将新文件的内容添加到旧列表中。如果定义了 HOSTFILE 但是没有赋值,bash 将尝试读取 /etc/hosts 文件来获得可能的主机名补全列表。当取消 HOSTFILE 的定义时,主机名列表将清空。
  • IFS 内部字段分隔符用来在扩展之后进行分词,使用内部命令 read 将行划分成词。默认值是 <space><tab><newline>
  • IGNOREEOF 控制交互式 shell 接受到唯一一个 EOF 字符时的行为。如果有定义,值是需要在一行的开始连续输入 EOF 字符,直到可以使 bash 退出的字符个数。如果这个变量存在,但是值不是一个数字或者没有赋值,默认值是 10。如果变量没有定义,EOF 标志着输入的结束。
  • INPUTRC 逐行读取的启动配置文件,而不是默认的 ~/.inputrc(参见下面的逐行读取库章节)
  • LANG 用来决定没有特地用 LC_ 变量指定的语言环境项
  • LC_ALL 这个变量超越了 LANG 和所有其他指定语言环境项的 LC_ 变量
  • LC_COLLATE 这个变量决定了为路径扩展的结果排序时的字母顺序,决定了范围表达式的行为, 等价类,和路径扩展中的归并顺序以及模式匹配
  • LC_CTYPE 这个变量决定了字符的解释和路径扩展以及模式匹配中字符类的行为
  • LC_MESSAGES 这个变量决定了翻译以 $ 前导的双引号字符串时的语言环境
  • LC_NUMERIC 这个变量决定了格式化数字时的语言环境分类
  • LINES 内建命令 select 用它来判断输出选择列表时的列宽度。在收到 SIGWINCH 信号时自动设置
  • MAIL 如果这个参数设置为一个文件名,并且没有设置环境变量 MAILPATH 的话,bash 将在这个文件中通知用户有邮件到达
  • MAILCHECK 指定 bash 检查邮件的频率是多少,以秒为单位。默认值是 60 秒。需要检查邮件的时候,shell 在显示提示符之前将进行检查。 如果取消它的定义,或者设置为并非大于等于零的数值,shell 将禁止邮件检查。
  • MAILPATH 一个冒号分隔的文件名列表,从中检查邮件。当邮件到达某个特殊文件中时,输出的特定消息可以通过将文件名与消息以 ? 分隔来指定。在消息的文本中,$_ 扩展为当前邮件文件的文件名。例如:MAILPATH='/var/mail/bfox?"You have mail":~/shell-mail?"$_ has mail!" Bash 为这个变量提供默认值,但是它使用的用户邮件文件的位置是系统相关的(例如,/var/mail/$USER)。
  • OPTERR 如果设置为 1,bash 显示内置命令 getopts 产生的错误消息(参见下面的内置命令章节)。每次 shell 启动时或者一个 shell 脚本被执行时 OPTERR 被初始化为 1。
  • PATH 搜索命令的路径。它是一个冒号分割的目录列表,shell 从中搜索命令(参见下面的命令执行章节)。默认的路径是系统相关的,是由安装 bash 的系统管理员设置的。通常它的值是 /usr/gnu/bin:/usr/local/bin:/usr/ucb:/bin:/usr/bin:.
  • POSIXLY_CORRECT 如果 bash 启动环境中有这个变量,它将在读取启动配置文件之前进入 posix mode,就好像提供了 --posix 启动参数一样。如果 shell 运行过程中设置了它,bash 就启用 posix mode,就好像执行了 set -o posix 命令一样。
  • PROMPT_COMMAND 如果有定义,它的值将作为一个命令,每次显示主提示符之前都会执行
  • PS1 这个参数的值被扩展(参见下面的提示符章节),用作主提示符字符串。默认值是 \s-\v\$
  • PS2 这个参数的值同 PS1 一起被扩展,用作次提示符字符串。默认值是 >
  • PS3 这个参数的值被用作内置命令 select 的提示符(参见上面的 Shell 语法章节)
  • PS4 这个参数的值同 PS1 一起被扩展,在执行跟踪中在 bash 显示每个命令之前显示。需要的话,PS4 的第一个字符会被复制多次,来指示 indirection 的层数。默认值是 +
  • TIMEFORMAT 在前缀 time 保留字的管道中,这个参数的值用作格式字符串, 指定计时信息如何显示。字符 % 引入的转义序列,被扩展为时间值或其他信息。转义序列和它们的含义如下所示;括号中是可选的成分。

    • %% 一个字面上的 %
    • %[p][l]R 经历的时间,以秒计算
    • %[p][l]U CPU 在用户模式下执行的秒数
    • %[p][l]S CPU 在系统模式下执行的秒数
    • %P CPU 使用率,算法是 (%U + %S) / %R

    可选的 p 是指定精度(小数点后数字位数)的数值。如果是 0 就不输出小数点或小数值。最多指定到小数点后三位;如果 p 大于3 就会被改为 3。如果没有指定 p,默认使用 3。

    可选的 l 指定了长格式,包含分钟,格式是 MMmSS.FFs。p 的值决定了是不是包含小数位。

    如果没有设置这个值,bash 假定它的值是 $'\nreal\t%3lR\nuser\t%3lU\nsys%3lS'。如果它是空值,就不会显示计时信息。显示格式字符串的时候,会加上一个前导的新行符。

  • TMOUT 如果设置为大于 0 的值,TMOUT 被当作内建命令 read 的默认超时等待时间。如果等待终端输入时,TMOUT 秒之后仍然没有输入, select 命令将终止。在交互的 shell 中,它的值被解释为显示了主提示符之后等待输入的秒数。如果经过这个秒数之后仍然没有输入,Bash 将退出。

  • auto_resume TMOUT 如果设置为大于 0 的值,TMOUT 被当作内建命令 read 的默认超时 等待时间。如果等待终端输入时, TMOUT 秒之后仍然没有输入,select 命令将终止。在交互的 shell 中,它的值被解释为显示了 主提示符之后等待输入的秒数。如果经过这个秒数之后仍然没有输入,Bash 将退出。

  • auto_resume 这个变量控制了 shell 如何与用户和作业控制交互。如果设置了这个变量,一个不包含重定向的单个词的简单命令,将作为恢复被中断的作业的指示。不允许出现模棱两可的情况;如果有多个作业都以这个词起始,将恢复最近运行的作业。在这种情形下,被中断的作业的 name 是用于启动它的命令行。如果值设置为 exact,给出的字符串必须精确匹配被中断的作业名;如果设置为 substring,给出的字符串需要匹配被中断的作业名的子串。值 substring 的功能与作业标识符 %? 功能类似(参见下面的作业控制章节)。如果设置为任何其他值,给出的字符串必须是被中断的作业的前缀;这样做与作业标识符 % 功能类似。

  • histchars 两到三个字符,控制着历史扩展和分段(参见下面的历史扩展章节)。第一个字符是历史扩展 字符,这个字符表明了历史扩展的开始,通常是 !。第二个字符是快速替换字符,它是重新运行上次输入的命令,但将命令中的字符串替换为另一个的简写,默认是 ^。可选的第三个字符是指示如果作为一个词的开始,那么一行中剩余字符是注释。通常这个字符是 #。历史注释字符使得对一行中剩余字符在历史替换中被跳过。它不一定使 shell 解释器将这一行的剩余部分当作注释。

12.4. 数组

Bash 提供了一维数组变量。任何变量都可以作为一个数组;内建命令 declare 可以显式地定义数组。数组的大小没有上限,也没有限制在连续对成员引用和 赋值时有什么要求。数组以整数为下标,从 0 开始。

如果变量赋值时使用语法 name[subscript]=value,那么就会自动创建数组。subscript 被当作一个算术表达式,结果必须是大于等于 0 的值。要显式地定义一个数组,使用 declare -a name(参见下面的内置命令章节)。也可以用 declare -a name[subscript] 这时 subscript 被忽略。数组变量的属性可以用内建命令 declarereadonly 来指定。每个属性对于所有数组元素都有效。

数组赋值可以使用复合赋值的方式,形式是 name=(value1 ... valuen),这里每个 value 的形式都是 [subscript]=stringstring 必须出现。如果出现了可选的括号和下标,将为这个下标赋值,否则被赋值的元素的下标是语句中上一次赋值的下标加一。下标从 0 开始。这个语法也被内建命令 declare 所接受。单独的数组元素可以用上面介绍的语法 name[subscript]=value 来赋值。

数组的任何元素都可以用 ${name[subscript]} 来引用。花括号是必须的,以避免和路径扩展冲突。如果 subscript@ 或是 *,它扩展为 name 的所有成员。这两种下标只有在双引号中才不同。在双引号中,${name[*]} 扩展为一个词,由所有数组成员的值组成,用特殊变量 IFS 的第一个字符分隔;${name[@]}name 的每个成员扩展为一个词。如果数组没有成员,${name[@]} 扩展为空串。这种不同类似于特殊参数 *@ 的扩展(参见上面的特殊参数段落)。${#name[subscript]} 扩展为 ${name[subscript]} 的长度。如果 subscript* 或者是 @,扩展结果是数组中元素的个数。引用没有下标数组变量等价于引用元素 0。

内建命令 unset 用于销毁数组。unset name[subscript] 将销毁下标是 subscript 的元素。unset name,这里 name 是一个数组,或者 unset name[subscript], 这里 subscript* 或者是 @,将销毁整个数组。

内建命令 declarelocalreadonly 都能接受 -a 选项,从而指定一个数组。内建命令 read 可以接受 -a 选项,从标准输入读入一列词来为数组赋值。内建命令 setdeclare 使用一种可以重用为输入的格式来显示数组元素。

13. 扩展

命令行的扩展是在拆分成词之后进行的。有七种类型的扩展:花括号扩展,波浪线扩展,参数和变量扩展,命令替换,算术扩展,词的拆分和路径扩展。扩展是按照上面的顺序进行的。

还有一种附加的扩展:进程替换只有在支持它的系统中有效。

只有花括号扩展,词的拆分和路径扩展在扩展前后的词数会发生改变;其他扩展总是将一个词扩展为一个词。唯一的例外是上面提到的 "$@""${name[@]}"(参见参数)。

*注释:**

  • 扩展是 Shell 的强大功能,各种扩展的使用频率都很高
  • Bash 的命令和参数之间是用空格隔开的,空格隔开的是词
  • 词里面的特殊符号会被展开(即:扩展),扩展完成后,再执行扩展后的命令
  • 扩展是 Shell 完成的,与将要执行的命令无关
  • 有些情况一个词会扩展为一个词,有些情况一个词扩展为多个词
  • 下面会逐一详细介绍每种扩展的规则和使用场景

13.1. 花括号扩展

花括号扩展是一种可能产生任意字符串的机制。这种机制类似于路径扩展,但是并不需要存在相应的文件。花括号扩展的模式是一个可选的前导字符,后面跟着一系列逗号分隔的字符串,包含在一对花括号中,再后面是一个可选的附言。前导被添加到花括号中的每个字符串前面,附言被附加到每个结果字符串之后,从左到右进行扩展。

花括号扩展可以嵌套。扩展字符串的结果没有排序;而是保留了从左到右的顺序。例如,a{d,c,b}e 扩展为 ade ace abe

花括号扩展是在任何其他扩展之前进行的,任何对其他扩展有特殊意义的字符都保留在结果中。它是严格字面上的。Bash 不会对扩展的上下文或花括号中的文本做任何语义上的解释。

正确的花括号扩展必须包含没有引用的左括号和右括号,以及至少一个没有引用的逗号。任何不正确的表达式都不会被改变。可以用反斜杠来引用 {, 来阻止将它们识别为花括号表达式的一部分。为了避免与参数扩展冲突,字符串 ${ 不被认为有效的组合。

这种结构通常用来简写字符串的公共前缀远比上例中为长的情况,例如:

mkdir /usr/local/src/bash/{old,new,dist,bugs}

或者:

chown root /usr/{ucb/{ex,edit},lib/{ex?.?*,how_ex}}

花括号扩展导致了与历史版本的 sh 的一点不兼容。在左括号或右括号作为词的一部分出现时,sh 不会对它们进行特殊处理,会在输出中保留它们。Bash 将括号从花括号扩展结果的词中删除。例如,向 sh 输入 file{1,2} 会导致不变的输出。同样的输入在 bash 进行扩展之后,会输出 file1 file2。如果需要同 sh 严格地保持兼容,需要在启动 bash 的时候使用 +B 选项,或者使用 set 命令加上 +B 选项来禁用花括号扩展(参见下面的内置命令章节)。

13.2. 波浪线扩展

如果一个词以没有引用的波浪线字符 ~ 开始,所有在第一个没有引用的斜线 / 之前的字符(或者是这个词的所有字符,如果没有没引用的斜线的话)都被认为是波浪线前缀。如果波浪线前缀中没有被引用的字符,那么波浪线之后的字符串被认为是登录名。如果登录名是空字符串,波浪线将被替换为 shell 参数 HOME 的值。如果没有定义 HOME,将替换为执行此 shell 的用户的个人目录。否则,波浪线前缀被替换为与指定登录名相联系的个人目录。

如果波浪线前缀是 ~+,将使用 shell 变量 PWD 的值来替换。如果波浪线前缀是 ~-,并且设置了 shell 变量 OLDPWD,将使用这个变量值来替换。如果在波浪线前缀中,波浪线之后的字符串由一个数字 N 组成,前缀可选的 + 或者 -,那么波浪线前缀将被替换为目录栈中相应的元素,就是将波浪线前缀作为参数执行内置命令 dirs 显示的结果。如果波浪线前缀中波浪线之后的字符是一个数字,没有前缀,那么就假定有一个 +

如果登录名不合法,或者波浪线扩展失败,这个词将不会变化。

在变量赋值中,对于 := 之后的字符串会立即检查未引用的波浪线前缀。这种情况下,仍然会进行波浪线扩展。因此,可以使用带波浪线的文件名来为 PATH, MAILPATH, 和 CDPATH 赋值,shell 将赋予扩展之后的值。

*注释:**

  • 在控制台逐行输入下面的命令,观察一下效果

    echo ~
    echo ~/tmp
    echo ~wangding
    echo ~root
    echo ~xyz
    cd /
    cd /bin
    cd /etc
    cd /proc
    echo ~+
    echo ~-
    echo ~+1
    echo ~+2
    echo ~+3
    echo ~-1
    echo ~-2
    echo ~-3
    
  • 把上面的 echo 换成 ls 观察一下效果

13.3. 参数扩展

字符 $ 引入了参数扩展,命令替换和算术扩展。要扩展的参数名或符号可能包含在花括号中,花括号可选的,但是可以使得要扩展的变量不会与紧随其后的字符合并,成为新的名称。

使用花括号的时候,匹配的右括号是第一个 },并且它没有被反斜杠引用或包含在一个引用的字符串中,也没有包含在一个嵌入的算术扩展,命令替换或是参数扩展中。

${parameter} 被替换为 parameter 的值。如果 parameter 是一个位置参数,并且数字多于一位时;或者当紧随 parameter 之后有不属于名称一部分的字符时,都必须加上花括号。

如果 parameter 的第一个字符是一个感叹号,将引进一层间接变量。bash 使用以 parameter 的其余部分为名的变量的值作为变量的名称;接下来新的变量被扩展,它的值用在随后的替换当中,而不是使用 parameter 自身的值。这也称为间接扩展。例外情况是下面讲到的 ${!prefix*}

下面的每种情况中,word 都要经过波浪线扩展,参数扩展,命令替换和算术扩展。如果不进行子字符串扩展,bash 测试一个没有定义或值为空的参数;忽略冒号的结果是只测试未定义的参数。

  • ${parameter:-word} 使用默认值。如果 parameter 未定义或值为空,将替换为 word 的扩展。否则,将替换为 parameter 的值。
  • ${parameter:=word} 赋默认值。如果 parameter 未定义或值为空,word 的扩展将赋予 parameter。parameter 的值将被替换。位置参数和特殊参数不能用这种方式赋值
  • ${parameter:?word} 显示错误,如果未定义或值为空。如果 parameter 未定义或值为空,word(或一条信息,如果 word 不存在)的扩展将写入到标准错误;shell 如果不是交互的,则将退出。否则,parameter 的值将被替换
  • ${parameter:+word} 使用可选值。如果 parameter 未定义或值为空,不会进行替换;否则将替换为 word 扩展后的值
  • ${parameter:offset}
  • ${parameter:offset:length} 子字符串扩展。扩展为 parameter 的最多 length 个字符,从 offset 指定的字符开始。如果忽略了 length,扩展为 parameter 的子字符串,从 offset 指定的字符串开始。length 和 offset 是算术表达式(参见下面的算术求值章节)。length 必须是一个大于等于 0 的数值。如果 offset 求值结果小于 0,值将当作从 parameter 的值的末尾算起的偏移量。如果 parameter 是 @,结果是 length 个位置参数,从 offset 开始。如果 parameter 是一个数组名,以 @ 或 * 索引,结果是数组的 length 个成员,从 ${parameter[offset]} 开始。子字符串的下标是从 0 开始的,除非使用位置参数时,下标从 1 开始。
  • ${!prefix*} 扩展为名称以 prefix 开始的变量名,以特殊变量 IFS 的第一个字符分隔
  • ${#parameter} 替换为 parameter 的值的长度(字符数目)。如果 parameter 是 或者是 @, 替换的值是位置参数的个数。如果 parameter 是一个数组名,下标是 或者是 @, 替换的值是数组中元素的个数。
  • ${parameter#word}
  • ${parameter##word} word 被扩展为一个模式,就像路径扩展中一样。如果这个模式匹配 parameter 的值的起始,那么扩展的结果是将 parameter 扩展后的值中,最短的匹配 # 的情况或者最长的匹配 ## 的情况删除的结果。如果 parameter 是 @ 或者是 ,则模式删除操作将依次施用于每个位置参数,最后扩展为结果的列表。如果 parameter 是一个数组变量,下标是 @ 或者是 ``,模式删除将依次施用于数组中的每个成员,最后扩展为结果的列表。
  • ${parameter%word}
  • ${parameter%%word} word 被扩展为一个模式,就像路径扩展中一样。如果这个模式匹配 parameter 扩展后的值的尾部,那么扩展的结果是将 parameter 扩展后的值中,最短的匹配 % 的情况或者最长的匹配 %% 的情况删除的结果。如果 parameter 是 @ 或者是 ,则模式删除操作将依次施用于每个位置参数,最后扩展为结果的列表。如果 parameter 是一个数组变量,下标是 @ 或者是 , 模式删除将依次施用于数组中的每个成员,最后扩展为结果的列表。
  • ${parameter/pattern/string}
  • ${parameter//pattern/string} patterm 被扩展为一个模式,就像路径扩展中一样。parameter 被扩展,其值中最长的匹配 pattern 的内容被替换为 string。在第一种形式中,只有第一个匹配被替换。第二种形式使得 pattern 中所有匹配都被替换为 string。如果 pattern 以 # 开始,它必须匹配 parameter 扩展后值的首部。如果 pattern 以 % 开始,它必须匹配 parameter 扩展后值的尾部。如果 string 是空值,pattern 的匹配都将被删除,pattern 之后的 / 将被忽略。如果 parameter 是 @ 或者是 , 则替换操作将依次施用于每个位置参数,最后扩展为结果的列表。如果 parameter 是一个数组变量,下标是 @ 或者是 , 模式删除将依次施用于数组中的每个成员,最后扩展为结果的列表。

13.4. 命令替换

命令替换允许以命令的输出替换命令名。有两种形式:$(command) 和 `command`

Bash 进行扩展的步骤是执行命令,以它的标准输出替换它,并且将所有后续的新行符删除。内嵌的新行符不会删除,但是它们可能会在词的拆分中被删除。命令替换 $(cat file) 可以用等价但是更快的方法 $(< file) 代替。

当使用旧式的反引号 ` 替换形式时,反斜杠只有其字面意义,除非后面是 $, `, 或者是 \。第一个前面没有反斜杠的反引号将结束命令替换。当使用 $(command) 形式时,括号中所有字符组成了整个命令;没有被特殊处理的字符。

命令替换可以嵌套。要在使用反引号形式时嵌套,可以用反斜杠来转义内层的反引号。

如果替换发生在双引号之中,结果将不进行词的拆分和路径扩展。

13.5. 算术扩展

算术扩展允许算术表达式的求值和结果的替换。算术扩展的格式是:$((expression))

表达式被视为如同在双引号之中一样,但是括号中的双引号不会被特殊处理。表达式中所有词都经过了参数扩展,字符串扩展,命令替换和引用的删除。算术替换可以嵌套。

求值根据下面算术求值章节中列出的规则进行。如果表达式非法,bash 输出错误提示消息,不会进行替换。

13.6. 进程替换

进程替换只有在支持命名管道 (FIFOs), 或者支持使用 /dev/fd 方式为打开的文件命名的系统中才可用。 它的形式是 <(list) 或者是 >(list)。 进程 list 运行时的输入或输出被连接到一个 FIFO 或者 /dev/fd 中的文件。文件的名称作为一个参数被传递到当前命令,作为扩展的结果。 如果使用 >(list) 形式,向文件写入相当于为 list 提供输入。如果使用 <(list) 形式,可以读作为参数传递 的文件来获得 list 的输出。

如果可能的话,进程替换是与参数和变量扩展,命令替换和算术扩展同时发生的。

13.7. 单词分割

shell 检测不在双引号引用中发生的参数扩展,命令替换和算术扩展的结果, 进行 word splitting(词的拆分)。

shell 将 IFS 的每个字符都作为定界符,根据这些字符来将其他扩展的结果分成词。如果 IFS 没有定义,或者它的值是默认的, 那么 IFS 字符的任何序列都将作为分界之用。如果 IFS 的值是默认之外的值,那么词开头和结尾的空白字符 space 和 tab 都将被忽略,只要空白字符在 IFS 的值之内 (即, IFS 包含空白字符)。 任何在 IFS 之中但是不是 IFS 空白的字符,以及任何相邻的 IFS 空白字符,将字段分隔开来。 IFS 空白字符的序列也被作为分界符。如果 IFS 的值是空,不会发生词的拆分。

显式给出的空值参数 ("" 或 '') 将被保留。 隐含的空值参数,来自于空值的参数扩展,如果没有引用则将被删除。如果空值的参数在双引号引用中扩展,结果是空值的参数,将被保留。

注意如果没有发生扩展,不会进行词的拆分。

13.8. 路径扩展

词的拆分之后,除非设置过 -f 选项,bash 搜索每个词,寻找字符 *, ?, 和 [。如果找到了其中之一,那么这个词被当作一个模式,被替换为匹配这个模式的文件名以字母顺序排列的列表。如果没有找到匹配的文件名,并且 shell 禁用了 nullglob 选项,这个词将不发生变化。如果设置了 nullglob 选项并且没有找到匹配,这个词将被删除。如果启用了 nocaseglob 选项,匹配时将不考虑字母的大小写。当模式用作路径名扩展时,字符 . 如果在一个名称的开始或者紧随一个斜杠之后,那么它必须被显式地匹配,除非设置了 dotglobshell 选项。当匹配一个路径名时,斜杠符必须被显式地匹配。其他情况下,字符 . 不会被特殊对待。参见下面的内置命令中对 shopt 的介绍,其中有 shell 选项 nocaseglobnullglobdotglob 的描述。

环境变量 GLOBIGNORE 可以用来限制匹配 pattern 的文件名集合。如果设置了 GLOBIGNORE,每个匹配的文件名如果匹配 GLOBIGNORE 中任何一个模式的话将从匹配的列表中删除。文件名 ... 总是被忽略,即使设置了 GLOBIGNORE。但是,设置 GLOBIGNORE 和启用 shell 选项 dotglob 效果是相同的,因此所有其他以 . 开头的文件名将被匹配。要得到原来的行为(忽略所有以 . 开头的文件名),可以将 .* 添加为 GLOBIGNORE 的模式之一。选项 dotglob 被禁用,如果 GLOBIGNORE 没有定义时。

*注释:**

  • 路径扩展中的特殊字符扩展,称为模式扩展(globbing)或模式匹配
  • 这个词来自于早期的 Unix 系统有一个/etc/glob文件,保存扩展的模板
  • 后来 Bash 内置了这个功能,但是这个名字就保留了下来
  • 模式扩展早于正则表达式出现,可以看作是原始的正则表达式
  • 它的功能没有正则那么强大灵活,但是优点是简单和方便

模式匹配

任何模式中出现的字符,除了下面描述的特殊模式字符外,都匹配它本身。模式中不能出现 NUL 字符。如果要匹配字面上的特殊模式字符,它必须被引用。

特殊模式字符有下述意义:

  • * 匹配任何字符串包含空串
  • ? 匹配任何单个字符
  • [...] 匹配所包含的任何字符之一。用一个连字符 - 分隔的一对字符意思是一个范围表达式;任何排在它们之间的字符,包含它们,都被匹配。排序使用当前语言环境的字符顺序和字符集。如果 [ 之后的第一个字符是一个 ! 或是一个 ^ 那么任何不包含在内的字符将被匹配。范围表达式中字符的顺序是由当前语言环境和环境变量 LC_COLLATE 的值(如果设置了的话)决定的。一个 - 只有作为集合中第一个或最后一个字符时才能被匹配。一个 ] 只有是集合中第一个字符时才能被匹配。

[] 中,字符类可以用 [:class:] 这样的语法来指定,这里 class 是在 POSIX.2 标准中定义的下列类名之一:

alnum alpha ascii blank cntrl digit graph lower print punct space upper word xdigit

一个字符类匹配任何属于这一类的字符。word 字符类匹配字母,数字和字符 _

[] 中,可以用 [=c=] 这样的语法来指定等价类。它匹配与字符 c 有相同归并权值(由当前语言环境定义)的字符。

[] 中,语法 [.symbol.] 匹配归并符号。

*注释:**

  • 假设当前目录有三个文件:a.txt、b.txt 和 ab.txt,控制台逐行执行下面的命令,观察效果

    ls ?.txt
    ls ??.txt
    ls *.txt
    ls *
    ls a*
    ls *b*
    cd ~
    ls .*
    cd /
    ls x*
    ls */*.txt
    ls **/*.txt
    ls [ab].*
    

如果使用内建命令 shopt 启用了 shell 选项 extglob,将识别另外几种模式匹配操作符。下面的描述中,pattern-list 是一个或多个模式以 | 分隔的列表。复合的模式可以使用一个或多个下列的子模式构造出来:

  • ?(pattern-list) 匹配所给模式零次或一次出现
  • *(pattern-list) 匹配所给模式零次或多次出现
  • +(pattern-list) 匹配所给模式一次或多次出现
  • @(pattern-list) 准确匹配所给模式之一
  • !(pattern-list) 任何除了匹配所给模式之一的字串

13.9. 引用移除

经过前面的扩展之后,所有未引用的字符 \, ', 以及并非上述扩展结果的字符 " 都被删除。

14. 重定向

14.1. 重定向输入

14.2. 重定向输出

14.3. 重定向输出的追加模式

15. 别名

别名机制允许将一个词来替换为一个字符串,如果它是一个简单命令的第一个词的话。shell 记录着一个别名列表,可以使用内建命令 alias 和 unalias 来定义和取消(参见下面的 内置命令章节)。每个命令的第一个词,如果没有引用,都将被检查是否是一个别名。如果是,这个词将被它所指代的文本替换。别名和替换的文本可以包含任何有效的 shell 输入,包含上面列出的 metacharacters(元字符),特殊情况是别名中不能包含 =。替换文本的第一个词也被检查是否是别名,但是如果它与被替换的别名相同,就不会再替换第二次。这意味着可以用 ls 作为 ls -F 的别名,bash 不会递归地展开替换文本。如果别名的最后一个字符是 blank,那么命令中别名之后的下一个词也将被检查是否能进行别名展开。

别名可以使用 alias 命令来创建或列举出来,使用 unalias 命令来删除。

在替换文本中没有参数机制。如果需要参数,应当使用 shell 函数(参见下面的函数章节)。

如果 shell 不是交互的,别名将不会展开,除非使用内建命令 shopt 设置了 expand_aliases 选项。

关于别名的定义和使用中的规则比较混乱。Bash 在执行一行中的任何命令之前,总是读入至少完整一行的输入。别名在命令被读取时展开,而不是在执行的时候。因此,别名定义如果和另一个命令在同一行,那么不会起作用,除非读入了下一行。别名定义之后,同一行中的命令不会受新的别名影响。这种行为在函数执行时存在争议,因为别名替换是在函数定义被读取时发生的,而不是函数被执行的时候,因为函数定义本身是一个复合命令。结果,在函数中定义的别名只有当这个函数执行完才会生效。为了保险起见,应当总是将别名定义放在单独的一行,不在复合命令中使用 alias。

不管什么情况下,别名都被 shell 函数超越。

16. 函数

一个 shell 函数,以上面 shell 语法中描述的方法定义,保存着一系列的命令,等待稍后执行。当 shell 函数名作为一个简单命令名使用时,这个函数名关联的命令的序列被执行。函数在当前 shell 的上下文环境中执行;不会创建新的进程来解释它们(这与 shell 脚本的执行形成了对比)。当执行函数时,函数的参数成为执行过程中的位置参数。特殊参数 # 被更新以反映这个变化。位置参数 0 不会改变。函数执行时,FUNCNAME 变量被设置为函数的名称。函数和它的调用者在 shell 执行环境的所有其他方面都是一样的,特殊情况是 DEBUG 陷阱(参见下面对内建函数 trap 的描述,在 shell 内建命令章节中)不会被继承,除非函数设置了 trace 属性(参见下面对内建函数 declare 的描述)。

函数中的局部变量可以使用内建命令 local 来声明。通常情况下,变量和它们的值在函数和它的调用者之间是共享的。

如果函数中执行了内建命令 return,那么函数结束,执行从函数调用之后的下一个命令开始。函数结束后,位置参数的值以及特殊参数 # 都将重置为它们在函数执行前的值。

函数名和定义可以使用内建命令 declare 或 typeset 加上 -f 参数来列出。如果在 declare 或 typeset 命令中使用 -F 选项将只列出函数名。函数可以使用内建命令 export 加上 -f 参数导出,使得子 shell 中它们被自动定义。

函数可以是递归的。对于递归调用的次数没有硬性限制。

17. 算术求值

在一定的环境下,shell 允许进行算术表达式的求值(参见内建命令 let算术扩展)。求值使用固定宽度的整数,不检查是否溢出,但是被零除会被捕获,标记为错误。操作数及其优先级和聚合程度与 C 语言中相同。下列操作数的列表按照相同优先级的操作数其级别来分组。列出的级别顺序是优先级递减的。

  • id++, id-- 变量自增,变量自减 (在后)
  • ++id, --id 变量自增,变量自减 (在前)
  • -, + (单目的)取负,取正
  • !, ~ 逻辑取反,位取反
  • ** 乘幂
  • *, /, % 乘,除,取余
  • +, - 加,减
  • <<, >> 左移位,右移位
  • <=, >=, <, > 关系运算的小于等于,大于等于,小于,大于
  • ==, != 比较运算的相等,不等
  • & 位与
  • ^ 位异或
  • | 位或
  • && 逻辑与
  • || 逻辑或
  • expr? expr: expr 条件求值
  • =, *=, /=, %=, +=, -=, <<=, >>=, &=, ^=, |= 各种赋值运算
  • expr1, expr2 逗号表达式

shell 变量可以作为操作数;在表达式求值之前会进行参数扩展。在表达式中,可以用名称引用 shell 变量,不必使用参数扩展的语法。变量被引用时,其值被作为算术表达式来求值。shell 变量用于表达式中时,不必启用整数属性。

以 0 为前导的常量被当作八进制数,以 0x 或 0X 作为前导表明是十六进制。其他情况下,数字的形式是 [base#]n,这里 base 是一个 2 到 64 的十进制数值,作为数字的基数,n 是在这个基数中数字的值。如果忽略了 base#,将以 10 为基数。大于 10 的数字依次以小写字母,大写字母,@ 和 _ 表示。如果 base 小于或等于 36,在表示 10 与 35 之间的数字时小写字母和大写字母可以互换。

操作符根据优先级顺序进行求值。圆括号中的子表达式被最先求值,可能会超越上面的优先级规则。

注释

  • 算术运算中的运算符以及相应的运算操作跟 C 语言一样
  • 这里的算术运算是广义的,狭义的算术运算只包括加、减、乘、除和取余运算
  • 其余的运算分别为关系运算,逻辑运算和位运算,等
  • 控制台逐行输入下面的命令,最好先心算,然后跟运行结果比对
    num=5; ((num++)); echo $num
    num=5; echo $((num++)); echo $num
    num=5; echo $((++num))
    num=5; echo $((-num))
    num=0; echo $((\!num))
    num=5; echo $((~num))
    num=3; echo $((num**2))
    echo $((2+3))
    echo $((2-3))
    echo $((2*3))
    echo $((2/3))
    echo $((12/3))
    echo $((11%3))
    echo $((8>>1))
    echo $((8<<1))
    echo $((8<1))
    echo $((8>1))
    echo $((2>=3))
    echo $((2<=3))
    echo $((2==3))
    echo $((2!=3))
    echo $((2&1))
    echo $((2&2))
    echo $((2|1))
    echo $((3^3))
    echo $((3^0))
    age=30; man=$((age>=20)); echo $((man?1:0))
    num=10; ((num+=5)); echo $num
    echo $((2+3, 2*3))
    

18. 条件表达式

条件表达式用于 [[ 复合命令以及内建命令 test 和 [ 中,用来测试文件属性,进行字符串和算术比较。表达式使用下面的单目或双目操作符构造。如果某操作的任何 file 参数的形式是 /dev/fd/n,那么将检查文件描述符 n。如果某操作的 file 参数是 /dev/stdin/dev/stdout 或者 /dev/stderr 之一,将分别检查文件描述符 0,1 和 2。

  • -a file 如果 file 存在则为真
  • -b file 如果 file 存在且为块设备则为真
  • -c file 如果 file 存在且为字符设备则为真
  • -d file 如果 file 存在且是一个目录则为真
  • -e file 如果 file 存在则为真
  • -f file 如果 file 存在且为普通文件则为真
  • -g file 如果 file 存在且是设置组 ID 的 (sgid) 则为真
  • -h file 如果 file 存在且为符号链接则为真
  • -k file 如果 file 存在且设置了粘滞位则为真
  • -p file 如果 file 存在且是一个命名管道 (FIFO) 则为真
  • -r file 如果 file 存在且可读则为真
  • -s file 如果 file 存在且大小大于零则为真
  • -t fd 如果文件描述符 fd 是打开的且对应一个终端则为真
  • -u file 如果 file 存在且是设置用户 ID 的 (suid) 则为真
  • -w file 如果 file 存在且可写则为真
  • -x file 如果 file 存在且可执行则为真
  • -O file 如果 file 存在且为有效用户 ID 所拥有则为真
  • -G file 如果 file 存在且为有效组 ID 所拥有则为真
  • -L file 如果 file 存在且为符号链接则为真
  • -S file 如果 file 存在且为套接字则为真
  • -N file 如果 file 存在且上次读取后被修改过则为真
  • file1 -nt file2 如果 file1 比 file2 要新 (根据修改日期),或者 如果 file1 存在而 file2 不存在,则为真
  • file1 -ot file2 如果 file1 比 file2 更旧,或者 如果 file1 不存在而 file2 存在,则为真
  • file1 -ef file2 如果 file1 和 file2 指的是相同的设备和 inode 号则为真
  • -o optname 如果启用了 shell 选项 optname 则为真。参见下面对内建命令 set 的 -o 选项的描述中的选项列表
  • -z string 如果 string 的长度为 0 则为真
  • -n string 如果 string 的长度非 0 则为真
  • string1 == string2 如果字符串相等则为真。= 可以用于使用 == 的场合来兼容 POSIX 规范
  • string1 != string2 如果字符串不相等则为真
  • string1 < string2 如果 string1 在当前语言环境的字典顺序中排在 string2 之前则为真
  • string1 > string2 如果 string1 在当前语言环境的字典顺序中排在 string2 之后则为真
  • arg1 OP arg2 OP 是 -eq, -ne, -lt, -le, -gt, 或 -ge 之一。这些算术双目操作返回真,如果 arg1 与 arg2 分别是 相等,不等,小于,小于或等于,大于,大于或等于关系。 Arg1 和 arg2 可以是正/负整数

注释:

  • 控制台逐行输入下面命令

    bash
    if [ -a /dev/stdin ]; then echo '/dev/stdin exist'; fi
    if [ -a /dev/abc ]; then echo '/dev/abc exist'; else echo '/dev/abc not exist'; fi
    
  • cat ~/.profile 内容如下:

    # if running bash
    if [ -n "$BASH_VERSION" ]; then
      # include .bashrc if it exists
      if [ -f "$HOME/.bashrc" ]; then
      . "$HOME/.bashrc"
      fi
    fi
    
    # set PATH so it includes user's private bin if it exists
    if [ -d "$HOME/bin" ] ; then
      PATH="$HOME/bin:$PATH"
    fi
    
    # set PATH so it includes user's private bin if it exists
    if [ -d "$HOME/.local/bin" ] ; then
      PATH="$HOME/.local/bin:$PATH"
    fi
    
  • 根据上面的列表,很容易读懂脚本代码中的 if 判断

  • [ -n "$BASH_VERSION" ],检查 $BASH_VERSION 字符串变量是否为空
  • [ -f "$HOME/.bashrc" ],检查 $HOME/.bashrc 文件是否存在且为普通文件
  • [ -d "$HOME/bin" ],检查 $HOME/bin 是否存在,并且是一个目录
  • [ -d "$HOME/.local/bin" ],检查 $HOME/.local/bin 是否存在,并且是一个目录
  • 把这些条件表达式代码的作用,跟代码前面的注释文字互相验证一下

19. 简单命令扩展

当执行一个简单命令时,shell 进行下列扩展,赋值和重定向,从左到右。

  1. 解释器标记为与变量赋值(在命令名之前的)和重定向有关的词被保存等待随后处理。
  2. 并非变量赋值或重定向的词被扩展。如果扩展后仍然有词保留下来,第一个词被作为命令名,其余词是参数。
  3. 重定向按照上面重定向中讲到的规则进行。
  4. 每个变量赋值中 = 之后的文本在赋予变量之前要经过波浪线扩展,参数扩展,命令替换,算术扩展和引用删除。

如果没有得到命令名,变量赋值影响当前 shell 环境。否则,变量被加入被执行的命令的环境中,不影响当前 shell 环境。如果任何赋值动作试图为只读变量赋值,将导致出错,命令以非零状态值退出。

如果没有得到命令名,重定向仍会进行,但是不影响当前 shell 环境。重定向出错将使命令以非零状态值退出。

如果扩展后有命令名保留下来,那么执行过程如下所示。否则,命令退出。如果在任何扩展中包含命令替换,那么整个命令的退出状态是最后一个命令替换的退出状态。如果没有进行命令替换,命令以状态零退出。

20. 命令执行

命令被拆分为词之后,如果结果是一个简单命令和可选的参数列表,将执行下面的操作。

如果命令名中没有斜杠,shell 试图定位命令位置。如果存在同名的 shell 函数,函数将被执行,像上面函数中讲到的一样。如果名 称不是一个函数,shell 从内建命令中搜索它。如果找到对应命令,它将被执行。

如果名称既不是 shell 函数也不是一个内建命令,并且没有包含斜杠,bash 搜索 PATH 的每个成员,查找含有此文件名(可执行文件)的目录。 Bash 使用散列表来储存可执行文件的全路径(参见下面的 shell 内建命令中的 hash。只有在散列表中没有找到此命令,才对 PATH 进行完整的搜索。如果搜索不成功,shell 输出错误消息,返回退出状态 127。

如果搜索成功,或者命令中包含一个或多个斜杠,shell 在单独的执行环境中执行这个程序。参数 0 被设置为所给名称;命令的其他参数被设置为所给的参数,如果有的话。

如果执行失败,因为文件不是可执行格式,并且此文件不是目录,就假定它是一个 shell 脚本,一个包含 shell 命令的文件。此时将孵化出一个子 shell 来执行它。子 shell 重新初始化自身,效果就好像是执行了一个新的 shell 来处理脚本一样,但是父 shell 保存的命令位置仍然被 保留(参见下面的 shell 内建命令中的 hash)。

如果程序是以 #! 开头的文件,那么第一行的其余部分指定了这个程序的解释器。shell 执行指定的解释器,如果操作系统不会自行处理这种可执行文件格式的话。解释器的参数由下面三部分组成:程序第一行中解释器名称之后的可选的一个参数,程序的名称,命令行参数,如果有的话。

21. 命令执行环境

shell 有执行环境的概念,由下列内容组成:

  • shell 启动时继承的打开的文件,例如在内建命令 exec 中使用重定向修改的结果
  • 当前工作目录,使用 cd,pushd 或者 popd 设置,或是由 shell 在启动时继承得到
  • 文件创建模式掩码,使用 umask 设置或是从 shell 的父进程中继承得到
  • 当前陷阱,用 trap 设置
  • shell 参数,使用变量赋值或者 set 设置,或者是从父进程的环境中继承得到
  • shell 函数,在执行中定义或者是从父进程的环境中继承得到
  • 设为允许的选项,在执行时设置(要么是默认允许的,要么是命令行给出的)或者是用 set 设置
  • 用 shopt 设为允许的选项
  • 用 alias 定义的 shell 别名
  • 各种进程号,包含后台作业的进程号,$$ 的值,以及 $PPID 的值

当并非 shell 函数或内置命令的简单命令执行时,它在一个由下述内容组成的单独的执行环境中启动。除非另外说明,值都是从 shell 中继承的。

  • shell 打开的文件,加上对命令使用重定向修改和添加的文件
  • 当前工作目录
  • 文件创建模式掩码
  • 标记为导出的 shell 变量,以及传递到环境中为这个命令导出的变量
  • shell 捕捉的陷阱被重置为从 shell 的父进程中继承的值,shell 忽略的陷阱也被忽略

在单独的环境中启动的命令不能影响 shell 的执行环境。

命令替换和异步命令都在子 shell 环境中执行。子 shell 环境是原有 shell 环境的赋值,但 shell 捕捉的陷阱被重置为 shell 启动时从父进程中继承的值。作为管道一部分来执行的内建命令也在一个子 shell 环境中执行。对子 shell 环境所作修改不能影响到原有 shell 的执行环境。

如果命令后面是 & 并且没有启用作业控制,命令的默认标准输入将是空文件 /dev/null。否则,被执行的命令从调用它的 shell 中继承被重定向修改的文件描述符。

22. 环境

当一个程序执行时,它被赋予一个字符串数组,成为环境 environment。 它是一个 名称-值对 (name-value) 的列表,形式是 name=value.

shell 提供了多种操作环境的方法。启动时,shell 扫描自身的环境,为每个找到 的名字创建一个参数,自动地将它标记为 export (向子进 程导出的)。被执行的命令继承了这个环境。 export 和 declare -x 命令允许参数和函数被加入到环境中或从环境中删除。如果环境中参数的 值 被修改,新值成为环境的一部分,替换了旧值。所有被执行的命令继承的环境 包含 shell 的初始环境 (可能值被修改过),减去被 unset 命令删除的,加上通过 export 和 declare -x 命令添加的部分。

可以在任何 simple command 或函数的环境中设定暂时有效的参数,只要将参数赋值放在命令前面就可以了, 参见上面 PARAMETERS 的描 述。这些赋值语句只在这个命令的环境中有效。

如果设置了内建命令 set 的 -k 选项, 所有的 变量赋值都将放到命令的环境中,不仅是在命令名前面的那些。

当 bash 执行一个外部命令时,变量 _ 被设置为命令的文件全名,然后被传递到命令的环境之中。

23. 退出状态

从 shell 的角度看,一个命令退出状态是 0 意味着成功退出。退出状态是 0 表明成功。非零状态值表明失败。当命令收到 fatal signal N 退出时,bash 使用 128+N 作为它的退出状态。

如果没有找到命令,为执行它而创建的子进程返回 127。如果找到了命令但是文件不可执行,返回状态是 126。

如果命令由于扩展或重定向错误而失败,退出状态大于零。

shell 内建命令如果成功返回 0(true),执行时出错则返回非零 (false)。所有内建命令返回 2 来指示不正确的用法。

Bash 自身返回最后执行的命令的退出状态,除非发生了语法错误,这时它返回非零值。参见下面置的内命令 exit

24. 信号

如果 bash 是交互的,没有设定任何陷阱,它忽略 SIGTERM(这样 kill 0 不会杀掉交互的 shell)。SIGINT 被捕获并处理(从而使内建命令 wait 可以中断)。在所有情况下,bash 忽略 SIGQUIT。如果正在使用作业控制,bash 忽略 SIGTTIN,SIGTTOU,和 SIGTSTP。

bash 开始的并行作业的信号处理句柄都设置为 shell 从父进程中继承的值。如果不是正在使用作业控制,异步命令还忽略 SIGINT 和 SIGQUIT。 作为命令替换结果运行的命令忽略键盘产生的作业控制信号 SIGTTIN,SIGTTOU,和 SIGTSTP。

如果收到信号 SIGHUP,shell 默认退出。在退出前,交互的 shell 向所有作业,运行的或停止的,发送 SIGHUP 信号。shell 向停止的作业发出 SIGCONT 信号来保证它们会收到 SIGHUP. 要阻止 shell 向特定的作业发送信号,应当使用内建命令 disown 将作业从作业表中删除(参见下面的 shell 内置命令(#内置命令)章节)或者使用 disown -h 来标记为不接受 SIGHUP。

如果使用 shopt 设置了 shell 选项 huponexit,在交互的登录 shell 退出时 bash 向所有作业发出 SIGHUP 信号。

当 bash 等待命令执行结束时,如果收到已设置了陷阱的信号,陷阱 (trap) 将不会执行,直到命令结束。当 bash 通过内建命令 wait 等待异步命令时,如果收到已设置了陷阱的信号,将使得内置命令 wait 立即以大于 128 的状态值返回。接着,陷阱将立即被执行。

25. 作业控制

作业控制指的是可以选择停止(suspend,挂起)进程执行,并且可以在之后继续(resume,恢复)执行的能力。用户一般在交互的人机界面中使用这种功能。界面是由系统的终端驱动和 bash 共同提供的。

shell 将每个管道分配给一个作业。它保存一个当前运行的作业表,可以用 jobs 命令来列出。当 bash 启动一个异步的作业时(background,后台执行),它输出这样的一行:

[1] 25647

表明这个作业的作业号是 1,与作业相关连的管道中最后一个进程的进程 ID 是 15647。管道中所有进程都是同一个作业的成员。Bash 使用作业概念作为作业控制的基础。

为简化作业控制的用户界面的实现,操作系统负责管理“当前终端的进程组”的概念。这个进程组的成员(进程组 ID 是当前终端进程组 ID 的进程)可以收到键盘产生的信号,例如 SIGINT。这些进程被称为前台的。后台进程是那些进程组 ID 与终端不同的进程;这些进程不会收到键盘产生的信号。只有前台进程可以从终端读或向终端写。后台进程试图读/写终端时,将收到终端驱动程序发送的 SIGTTIN (SIGTTOU) 信号。这个信号如果没有加以捕捉,将挂起这个进程。

如果 bash 运行其上的操作系统支持作业控制,bash 会包含使用它的设施。在一个进程正在运行的时候键入挂起字符(通常是 ^Z,Control-Z)将使这个进程暂停,将控制权还给 bash。输入延时挂起字符(通常是 ^Y,Control-Y)将使这个进程在试图从终端读取输入时暂停,将控制权还给 bash. 用户接下来可以控制此作业的状态,使用 bg 命令使它在后台继续运行,fg 命令使它在前台继续运行,或 kill 命令将它杀死。^Z 会立即起作用,并且还有使等待中的输出和输入被忽略的附加副作用。

有很多方法来指代 shell 中的作业。字符 % 可以引入作业名。编号为 n 的作业可以用 %n 的形式来指代。作业也可以用启动它的名称的前缀,或者命令行中的子字符串来指代。例如,%ce 指代一个暂停的 ce 作业。如果前缀匹配多于一个作业,bash 报错。另一方面,使用 %?ce,可以指代任何命令行中包含字符串 ce 的作业。如果子字符串匹配多于一个作业,bash 报错。符号 %%%+ 指代 shell 意义上的当前作业,也就是前台被暂停的最后一个作业,或者是在后台启动的作业。前一作业可以使用 %- 来指代。在有关作业的输出信息中(例如,命令 jobs 的输出),当前作业总是被标记为 +, 前一作业标记为 -

简单地给出作业名,可以用来把它放到前台:%1fg %1 的同义词,将作业 1 从后台放到前台。类似的,%1 & 在后台恢复作业 1,与 bg %1 等价。

当某个作业改变状态时,shell 立即可以得知。通常,bash 等待直到要输出一个提示符时,才会报告作业的状态变化,从而不会打断其他输出。如果启用了内建命令 set-b 选项,bash 将立即报告这些变化。对 SIGCHLD 信号的陷阱将在每个子进程退出时执行。

如果在作业暂停时试图退出 bash,shell 打印一条警告消息。命令 jobs 可能被用来检查作业的状态。如果再次试图退出,中间没有其他命令,shell 不会打印其他警告,暂停的作业将终止。

26. 提示符

在交互执行时,bash 在准备好读入一条命令时显示主提示符 PS1,在需要更多的输入来完成一条命令时显示 PS2。Bash 允许通过插入一些反斜杠转义的特殊字符来定制这些提示字符串,这些字符被如下解释:

  • \a 一个 ASCII 响铃字符 (07)
  • \d 日期,格式是 "星期 月份 日" (例如,"Tue May 26")
  • \D{format} format 被传递给 strftime(3),结果被插入到提示字符串中; 空的 format 将使用语言环境特定的时间格式。花括号是必需的
  • \e 一个 ASCII 转义字符 (033)
  • \h 主机名,第一个 . 之前的部分
  • \H 主机名
  • \j shell 当前管理的作业数量
  • \l shell 的终端设备名的基本部分
  • \n 新行符
  • \r 回车
  • \s shell 的名称, $0 的基本部分 (最后一个斜杠后面的部分)
  • \t 当前时间,采用 24小时制的 HH:MM:SS 格式
  • \T 当前时间,采用 12小时制的 HH:MM:SS 格式
  • \@ 当前时间,采用 12小时制上午/下午 (am/pm) 格式
  • \A 当前时间,采用 24小时制上午/下午格式
  • \u 当前用户的用户名 the username of the current user
  • \v bash 的版本 (例如,2.00)
  • \V bash 的发行编号,版本号加补丁级别 (例如,2.00.0)
  • \w 当前工作目录
  • \W 当前工作目录的基本部分
  • \! 此命令的历史编号
  • \# 此命令的命令编号
  • \$ 如果有效 UID 是 0,就是 #, 其他情况下是 $
  • \nnn 对应八进制数 nnn 的字符
  • \\ 一个反斜杠
  • \[ 一个不可打印字符序列的开始,可以用于在提示符中嵌入终端控制序列
  • \] 一个不可打印字符序列的结束

命令编号和历史编号通常是不同的:历史编号是命令在历史列表中的位置,可能包含从历史文件中恢复的命令(参见下面的历史章节),而命令编号是当前 shell 会话中执行的命令序列中,命令的位置。字符串被解码之后,它将进行扩展,要经过 parameter expansion,command substitution, arithmetic expansion 和 quote removal, 最后要经过 shell 选项 promptvars 处理(参见下面的 shell 内建命令章节中,对命令 shopt 的描述)。

27. 逐行读取库

这是在交互 shell 中处理读取输入的库,除非在 shell 启动时给出了 --noediting 选项。默认情况下,行编辑命令类似于 emacs 中的那些。也可以使用 vi 样式的行编辑界面。要在 shell 运行之后关闭行编辑,使用内置命令 set+o emacs+o vi 选项(参见下面的内置命令章节)。

注释:

  • 这部分内容几乎都是交互式 Shell 的快捷操作
  • 跟非交互式 Shell 或 Shell 编程无关
  • 掌握这些内容会极大提高命令行操作的效率

27.1. 符号说明

在这个小节中,将使用 emacs 样式的记法来表述按键。Ctrl 键记为 C-key,例如,C-n 意思是 Ctrl-N。类似的,meta 键记为 M-key,因此 M-x 意味着 Meta-x。(在没有 meta 键的键盘上,M-x 意思是 ESC-x,也就是说,按下 Esc 键,然后按 x 键。这使得 Esc 成为 meta prefixM-C-x 的组合意思是 Esc-Ctrl-x,也就是按 Esc 键,然后按住 Ctrl 键,同时按 x 键。)

逐行读取命令可以有数字的参数,一般作为重复的计数。有些时候,它是重要参数的标记。给向前方进行的命令(例如,kill-line)传递负数参数,将使得命令向反方向进行。下面的命令如果接受参数时的行为与此不同,将另行说明。

当命令被描述为剪切(killing)文本时,被删除的文本被保存,等待将来粘贴(yanking)。被剪切的文本保存在 kill ring 中。连续的剪切使得文本被依次加入到一个单元中,可以一次被粘贴。不剪切文本的命令将 kill ring 中的文本分离。

注释:

  • meta 键可以在 xshell 终端软件中进行设置,通常设置成左 alt

27.2. 初始化

逐行读取可以通过将命令放入初始化文件(inputrc 文件)来定制。文件名从变量 INPUTRC 的值中获取。如果没有设置这个变量,默认是 ~/.inputrc。当逐行读取库的程序启动时,将读取初始化文件,按键关联和变量将被设置。逐行读取初始化文件中只允许有很少的基本构造。空行被忽略。以 # 开始的行是注释。以 $ 开始的行指示了有条件的构造。其他行表示按键关联和变量设置。

默认的按键关联可以使用 inputrc 文件改变。其他使用这个库的程序可以添加它们自己的命令和关联。

例如,将 M-Control-u: universal-argumentC-Meta-u: universal-argument 放入 inputrc 将使得 M-C-u 执行命令 universal-argument

可以识别下列字符的符号名称:RUBOUT, DEL, ESC, LFD, NEWLINE, RET, RETURN, SPC, SPACE, 和 TAB

在命令名之外,逐行读取允许将按键与一个字符串关联,当按下这个键时,将插入这个字符串(一个宏,macro)。

注释:

  • inputrc 文件有两个,/etc/inputrc~/.inputrc,如果后一个文件没有,自己创建一个
  • 通过 inputrc 可以自定义快捷键,定义的语法:按键: 动作,详细说明见下面的按键绑定
  • 注意可以支持的动作列表见命令名称之后

27.3. 按键绑定

inputrc 文件中的控制按键关联的语法非常简单。需要的内容是命令名或宏,以及它应当关联到的按键序列。名称可以以两种方式指定:一个按键的符号名称,可能带有 Meta-Control- 前缀,或者是一个按键序列。

当使用 keyname:function-namemacro 形式时,keyname 是按键以英文拼写的名称。例如:

Control-u: universal-argument
Meta-Rubout: backward-kill-word
Control-o: "> output"

在上述例子中,C-u 被关联到函数 universal-argumentM-DEL 被关联到函数 backward-kill-word,而 C-o 被关联为运行右边给出的宏(意思是,将向行中插入 "> output")。

在第二种形式中,"keyseq":function-namemacrokeyseq 不同于上面的 keyname,表示整个按键序列的字符串可以通过将按键序列放在双引号引用中来指定。可以使用一些 GNU Emacs 样式的按键序列,如下例所示,但是不会识别按键的符号名称。

"\C-u": universal-argument
"\C-x\C-r": re-read-init-file
"\e[11~": "Function Key 1"

在上述例子中,C-u 被又一次关联到函数 universal-argumentC-x C-r 被关联到函数 re-read-init-file,而 ESC[11~ 被关联为插 入文本 Function Key 1

GNU Emacs 样式的转义序列的全集为:

  • \C- Ctrl 前缀
  • \M- Meta 前缀
  • \e 一个 Esc 字符
  • \\ 反斜杠
  • \" 字面上的 "
  • \' 字面上的 '

除了 GNU Emacs 样式的转义序列,还有一系列反斜杠转义序列可用:

  • \a 响铃
  • \b 回退
  • \d 删除
  • \f 进纸
  • \n 新行符
  • \r 回车
  • \t 水平跳格
  • \v 竖直跳格
  • \nnn 一个八比特字符,它的值是八进制值 nnn(一到三个八进制数字)
  • \xHH 一个八比特字符,它的值是十六进制值 HH(一到两个十六进制数字)

输入宏的文本时,必须使用单引号或双引号引用来表明是宏的定义。没有引用的文本被当作函数名。在宏的定义体中,上述反斜杠转义被扩展。 反斜杠将引用宏文本中所有其他字符,包括 "'

Bash 允许使用内建命令 bind 来显示和修改当前逐行读取按键关联。在交互使用中可以用内建命令 set 的 -o 选项切换到编辑模式(参见下面的 shell 内置命令章节)。

注释:

  • 编辑 ~/.inputrc 文件,内容为一行 Control-o: backward-kill-line,bash 下体验 c-o 按键效果,跟 c-u 相同
  • 编辑 ~/.inputrc 文件,内容为一行 "\C-o": backward-kill-line,bash 下体验 c-o 按键效果,跟 c-u 相同
  • 两种格式都可以定义按键绑定,/etc/inputrc 用的是第二种,个人感觉第一种可读性更好一些
  • 当然有些情况,如果使用下面的特殊按键,则必须使用第二种格式

    "\e[2~"  代表 Insert 键
    "\e[3~"  代表 Delete 键
    "\e[4~"  代表 End 键
    "\e[5~"  代表 PageUp 键
    "\e[6~"  代表 PageDown 键
    "\e[7~"  代表 Home 键
    "\e[8~"  代表 End 键
    "\e[11~" 代表 F1 键
    "\e[12~" 代表 F2 键
    "\e[13~" 代表 F3 键
    ...      依此类推
    
  • 一行是一个按键定义,冒号前面是按键,冒号后面是动作,分两种:函数和宏

  • 函数名不能写错,函数名是已经定义好的,都有哪些函数名,见下面命令名称之后的内容
  • 宏就是通过按键一次性放到控制台的字符串,宏字符串需要使用单引号或者双引号,不加引号视为函数名
  • 宏可以用来把快捷键绑定到一个命令上,例如:"\C-o": "ls -l\n"

27.4. 变量

逐行读取包含额外的可用于定制它的行为的变量。可以在 inputrc 文件中设置变量,使用如下形式的语句:

set variable-name value

除非另外说明,变量的值总是 OnOff。变量和它们的默认值是:

  • bell-style (audible) 控制了当逐行读取需要鸣终端响铃时的动作。如果设置为 none,逐行读取不会鸣铃。如果设置为 visible,逐行读取使用可视的响铃,如果可用的话。如果设置为 audible,逐行读取试着鸣终端响铃。
  • comment-begin (#) 这个字符串在执行逐行读取命令 insert-comment 时被插入。这个命令在 emacs 模式下被关联为 M-#,在 vi 模式下是 #
  • completion-ignore-case (Off) 如果设置为 On,逐行读取进行大小写不敏感的文件名匹配和补全。
  • completion-query-items (100) 这个变量决定着何时向用户询问,是否查看由命令 possible-completions 产生的可能的补全数量。它可以设为任何大于或等于 0 的值。如果可能的补全数量大于或等于这个变量的值,用户将被提示是否愿意查看它们;否则将直接在终端上列出它们。
  • convert-meta (On) 如果设置为 On,逐行读取将把设置了最高位的字符转换为 ASCII 按键序列,方法是去掉第八位,前缀一个转义字符(实际上,使用 Esc 作为转义符 meta prefix)。
  • disable-completion (Off) 如果设置为 On,逐行读取将禁止词的补全。补全字符将被插入到行中,就好像它们被映射为 self-insert。
  • editing-mode (emacs) 控制逐行读取的按键关联集合与 emacs 还是 vi 相似。editing-mode 可以设置为 emacs 或 vi。
  • enable-keypad (Off) 如果设置为 On,逐行读取在调用时将试图启用辅助键盘。一些系统需要设置这个来启用方向键。
  • expand-tilde (Off) 如果设置为 On,逐行读取试图进行词的补全时会进行波浪线扩展。
  • history-preserve-point 如果设置为 On,历史代码试着在 previous-history 或 next-history 取回的每个历史行的相同位置中加点。
  • horizontal-scroll-mode (Off) 如果设置为 On,将使得逐行读取使用单行来显示,如果它比屏幕宽度要长,就在单一的屏幕行上水平滚动输入行,而不是自动回绕到新行。
  • input-meta (Off) 如果设置为 On,逐行读取将允许八比特输入(也就是说,它不会将它读入的字符中最高位删除)。不管它能支持什么样的终端要求。名称 meta-flag 与此变量同义。
  • isearch-terminators (C-[C-J) 用于终止增量的搜索,不再将字符当作命令执行的字符串。如果这个变量没有赋值,字符串 Esc 和 C-J 将终止增量的搜索。
  • keymap (emacs) 设置当前逐行读取键盘映射。有效的键盘映射名称是 emacs,emacs-standard,emacs-meta,emacs-ctlx,vi,vi-command,还有 vi-insert。vi 等价于 vi-command;emacs 等价于 emacs-standard。默认值是 emacs;editing-mode 的值也会影响默认的键盘映射。
  • mark-directories (On) 如果设置为 On,补全的目录名会添加一个斜杠。
  • mark-modified-lines (Off) 如果设置为 On,已被修改的历史行将显示为前缀一个星号(*)。
  • mark-symlinked-directories (Off) 如果设置为 On,补全的名称如果是到目录的符号链接,则将添加一个斜杠(与 mark-directories 的值同样处理)。
  • match-hidden-files (On) 这个变量,如果设置为 On,将使得逐行读取在进行文件名补全时,匹配以 . 开头的文件(隐藏文件)。除非用户在要补全的文件名中给出了前导的 .
  • output-meta (Off) 如果设置为 On,逐行读取将直接显示设置了第八位的字符,而不是转化为一个带 meta 前缀的转义序列。
  • page-completions (On) 如果设置为 On,逐行读取将使用内建的类似 more 的分页程序,来每次显示一屏可能的补全。
  • print-completions-horizontally (Off) 如果设置为 On,逐行读取将匹配的补全按字母表顺序排序,然后水平排列显示出来,而不是在屏幕上竖直排列显示。
  • show-all-if-ambiguous (Off) 这将调整补全函数的默认行为。如果设置为 on,拥有多于一个可能的补全的词将立即列出所有匹配,而不是鸣响铃。
  • visible-stats (Off) 如果设置为 On,在列出可能的补全时,将在文件名后面添加一个表示文件类型的字符,文件类型由 stat(2) 报告。

注释:

  • 基本没啥用,如果需要可以设置一下,但是最好搞明白每一项的用途
  • /etc/inputrc 文件中,使用了几个设置,可以对照上面的列表解读一下

27.5. 条件结构

逐行读取实现了一种功能,本质上与 C 预处理器进行条件编译的功能类似,允许根据测试的结果进行键盘关联和变量设置。其中使用了四种解释器指令。

  • $if $if 结构允许根据编辑模式,正在使用的终端,使用逐行读取程序来设定按键关联。测试的文本包括一行,直到行尾;不必用字符来隔离它。

    • mode $if 结构的 mode= 形式用于测试逐行读取处于 emacs 还是 vi 模式。这可以与命令 set keymap 结合使用,例如,设置 emacs-standard 和 emacs-ctlx 键盘映射,仅当逐行读取以 emacs 模式启动。

    • term term= 形式用于包含与终端相关的按键关联,也许是将按键序列输出与终端的功能键相关联。等号 = 右边的词被同终端的全名和名称中第一个 - 前面的一部分相比较。例如,允许 sun 同时匹配 sun 和 sun-cmd。

    • application 应用结构用于包含应用程序相关的设置。每个使用逐行读取的程序都设置 application name,初始化文件可以测试它的值。它可用于将一个按键序列与对特定的程序有用的功能相关联。例如,下列命令添加了一个按键序列,用以引用 bash 中当前的词或前一个词

      $if Bash
      # Quote the current or previous word
      "\C-xq": "\eb\"\ef\""
      $endif
      
  • $endif 上例中的这个命令,结束了一个 $if 命令。

  • $else 如果测试失败,$if 指令中这个分支的命令将被执行。
  • $include 这个指令使用单个文件名作为参数,从文件中读取命令和按键关联。例如,下列指令将读取 /etc/inputrc

    $include  /etc/inputrc
    

注释:

  • 基本没啥用,如果需要可以用一下
  • /etc/inputrc 文件中,使用了 $if 嵌套

27.6. 查找

逐行读取提供了从命令历史中搜索包含给定字符串的行的命令(参见下面的历史章节)。有两种搜索模式:增量和非增量。

增量搜索在用户结束输入搜索字符串时开始。在搜索字符串的每个字符被输入的同时,逐行读取显示与已输入的字符串匹配的下一个历史条目。增量的搜索只要求输入能找到期望的历史条目所需的那么多字符。isearch-terminators 变量中的字符用来终止一次增量的搜索。如果这个变量没有被赋值,EscCtrl-J 字符串将结束一次增量的搜索。Ctrl-G 将取消一次增量的搜索,恢复初始的行。当搜索终止时,包含搜索字符串的历史条目成为当前行。

要从历史列表中找到其他匹配的条目,适当地键入 Ctrl-SCtrl-R。这样将在历史中向前/向后搜索下一个匹配已输入的搜索字符串的条目。其他关联到某个逐行读取命令的按键序列将终止搜索并执行关联的命令。例如,newline 将终止搜索,接受当前行,从而执行历史列表中的命令。

逐行读取可以记住上次增量搜索的字符串。如果键入两次 Ctrl-R,中间没有输入任何字符来定义一个新的搜索字符串,那么将使用已记住的搜索字符串。

非增量的搜索将整个搜索字符串读入,然后才开始搜索匹配的历史条目。搜索字符串可以由用户输入,或者是当前行的内容的一部分。

注释:

  • 增量搜索是提高命令行操作的利器,尤其是输入较长的命令,如:docker 命令或 git 命令
  • 要把增量搜索命令变成使用习惯,只要是长一些的命令就用增量搜索

27.7. 命令名称

下面列出的是命令的名称以及默认情况下它们关联的按键序列。命令名称如果没有对应的按键序列,那么默认是没有关联的。在下列描述中,点 (point) 指当前光标位置,标记 (mark) 指命令 set-mark 保存的光标位置。point 和 mark 之间的文本被称为范围 (region)。

注释:

  • 上面逐行读取库的内容主要用来自定义快捷键,使用场合比较少,因为下面有大量预定义的快捷键
  • 从这里开始才是逐行读取库的重点和核心内容,经常用到的快捷键要记住,并且要经常使用

27.8. 移动光标

  • beginning-of-line (C-a):移到行首
  • end-of-line (C-e): 移到行尾
  • forward-char (C-b): 向行首移动一个字符
  • backward-char (C-f): 向行尾移动一个字符
  • forward-word (M-f): 移动到当前单词的词尾
  • backward-word (M-b): 移动到当前单词的词首
  • clear-screen (C-l): 清除屏幕,保留当前行在屏幕顶端

注释:

  • 这些快捷键使用频率很高,在控制台输入 echo hello world 命令,练习光标的移动操作
  • 快捷键里的 b 代表 backword 向后,f 代表 forward 向前,e 代表 end 行尾,a 代表 ahead 行首
  • 快捷键前面是动作函数名,如果要重新定义定义快捷键可以用到,以下相同不再赘述
  • .

  • redraw-current-line: 刷新当前行

注释:

  • 这个刷新当前行的操作,因为很少使用,所以没有定义快捷键

27.9. 操作历史行

  • previous-history (C-p):显示上一个历史命令
  • next-history (C-n):显示下一个历史命令
  • non-incremental-reverse-search-history (M-p):从当前行开始向后,使用非增量搜索来查找用户给出的字符串
  • non-incremental-forward-search-history (M-n):从当前行开始向前,使用非增量搜索来查找用户给出的字符串
  • reverse-search-history (C-r):从当前行开始向后搜索,按照需要在历史中向“上”移动。这是一个增量的搜索
  • forward-search-history (C-s):从当前行开始向前搜索,按照需要在历史中向“下”移动。这是一个增量的搜索
  • operate-and-get-next (C-o):执行历史文件里面的当前条目,并自动显示下一条命令。这对重复执行某个序列的命令很有帮助
  • yank-last-arg (M-.) 插入前一个命令的最后一个参数 (上一历史条目的最后一个词)。有参数时,行为类似于 yank-nth-arg。后继的 yank-last-arg 调用将 从历史列表中向后移动,依次将每行的最后一个参数插入
  • insert-last-argument (M-.):与 yank-last-arg 同义
  • accept-line (Newline, Return):接受这一行,不管光标在什么位置。如果行非空,将根据变量 HISTCONTROL 的状态加入到历史列表中。如果行是修改过的历史行,将恢复该历史行到初始状态。
  • edit-and-execute-command (C-x C-e):动一个编辑器,编辑当前命令行,将结果作为 shell 命令运行。 Bash 将依次试着运行 $FCEDIT, $EDITOR, 和 emacs 作为编辑器

注释:

  • 上面这些操作历史行的快捷键使用频率很高,通过试验理解每个快捷键的作用,然后记住并在平时大量使用
  • .

  • beginning-of-history (M-)<:显示第一个命令

  • end-of-history (M-)>:显示最后一个命令,即当前的命令
  • history-search-forward:从当前行开始向前搜索历史,查找从当前行首到 point 之间的字符串。这是一个非增量的搜索
  • history-search-backward:从当前行开始向后搜索历史,查找从当前行首到 point 之间的字符串。这是一个非增量的搜索
  • yank-nth-arg (M-C-y):将前一个命令的第一个参数 (通常是上一行的第二个词) 插入到 point 位置。有参数 n 时,将前一个命令的第 n 个词 (前一个命令中的词从 0 开始计数) 插入到 point 位置。负数参数则插入前一个命令倒数第 n 个词
  • shell-expand-line (M-C-e):扩展行,像 shell 做的那样。其中包含别名和历史扩展,还有所有的 shell 词的扩展。 参见下面的历史扩展中相关描述
  • history-expand-line (M-^):在当前行进行历史扩展。参见下面的历史扩展中相关描述
  • magic-space:在当前行进行历史扩展,并插入一个空格。参见下面的历史扩展中相关描述
  • alias-expand-line:在当前行进行别名扩展,参见上面的别名中相关描述
  • history-and-alias-expand-line:在当前行进行历史和别名扩展

注释:

  • 上面这些操作历史行的快捷键使用频率很低,有所了解即可

27.10. 修改文本

  • delete-char (C-d): 删除光标位置的字符
  • transpose-chars (C-t):光标位置的字符与它前面一位的字符交换位置
  • transpose-words (M-t):光标位置的词与它前面一位的词交换位置
  • upcase-word (M-l): 将光标位置至词尾转为小写
  • downcase-word (M-u): 将光标位置至词尾转为大写
  • tab-insert (C-v TAB): 插入一个跳格符号
  • self-insert (a, b, A, 1, !, ...):插入键入的字符

注释:

  • 上面这些修改文本的快捷键使用频率很高,通过试验理解每个快捷键的作用,然后记住并在平时大量使用

  • backward-delete-char (Rubout):删除光标之后的字符。当给出一个数值的参数时,保存删除的文本到 kill ring 中

  • forward-backward-delete-char:删除光标下的字符,除非光标在行尾,此时删除光标后的字符
  • quoted-insert (C-q, C-v):将输入的下一字符保持原样添加到行中。例如,可以用它来插入类似 C-q 的字符
  • capitalize-word (M-c):将当前 (或下一个) 词变为首字大写。有负值的参数时,将前一个词变为首字大写, 但是不移动 point
  • overwrite-mode:控制插入/改写模式。给出一个正整数参数时,切换为改写模式。给出一个非正数参数时,切换为插入模式。这个命令只影响 emacs 模式;vi 模式的改写与此不同。每个对逐行读取的调用都以插入模式开始。在改写模式下,关联到 self-insert 的字符替换 point 处的字符,而不是将它推到右边。关联到 backward-delete-char 的字符以空格替换 point 前的字符。默认情况下,这个命令没有关联。

注释:

  • 上面这些修改文本的快捷键使用频率很低,有所了解即可
  • .

27.11. 剪切和粘贴

  • unix-word-rubout (C-w): 删除光标前面的单词
  • kill-line (C-k): 剪切光标位置到行尾的文本
  • unix-line-discard (C-u):剪切光标位置到行首的文本
  • kill-word (M-d): 剪切光标位置到词尾的文本
  • kill-word (M-Backspace):剪切光标位置到词首的文本
  • yank (C-y): 在光标位置粘贴文本

注释:

  • 上面这些剪切粘贴的快捷键使用频率很高,通过试验理解每个快捷键的作用,然后记住并在平时大量使用

  • backward-kill-line (C-x Rubout):反向剪切到行首

  • kill-whole-line:剪切当前行中所有字符,不管 point 在什么位置
  • backward-kill-word (M-Rubout):剪切 point 之后的词。词的边界与 backward-word 使用的相同
  • delete-horizontal-space (M-\):删除 point 两边的所有空格和跳格
  • kill-region:剪切当前 region 的文本
  • copy-region-as-kill:将 region 的文本复制到剪切缓冲区中
  • copy-backward-word:将 point 前面的词复制到剪切缓冲区中。词的边界与 backward-word 使用的相同
  • copy-forward-word:将 point 之后的词复制到剪切缓冲区中。词的边界与 backward-word 使用的相同
  • yank-pop (M-y):轮转 kill-ring,粘贴新的顶部内容。只能在 yank 或 yank-pop 之后使用

注释:

  • 上面这些剪切粘贴的快捷键使用频率很低,有所了解即可

27.12. 补全

  • complete Tab: 完成自动补全

注释:

  • 补全知道用 tab 键就好了,不行就按两下 tab 键
  • 当然,补全最常见的是路径补全,cd (tab)(tab) 试一下
  • 除了路径补全还有变量名补全,echo $(tab)(tab) 试一下
  • 命令补全,! (tab)(tab) 试一下
  • .

  • possible-completions (M-?):列出可能的补全,与连按两次 Tab 键作用相同

  • complete-filename (M-/): 尝试文件路径补全
  • complete-command (M-!): 命令补全
  • complete-username (M-~): 用户名补全
  • complete-variable (M-$): 变量名补全
  • complete-hostname (M-@): 主机名补全
  • possible-filename-completions (C-x /):先按 Ctrl + x,再按 /,等同于 Alt + ?,列出可能的文件路径补全
  • possible-command-completions (C-x !):先按 Ctrl + x,再按 !,等同于 Alt + !,命令补全
  • possible-username-completions (C-x ~):先按 Ctrl + x,再按 ~,等同于 Alt + ~,用户名补全
  • possible-variable-completions (C-x $):先按 Ctrl + x,再按 $,等同于 Alt + $,变量名补全
  • possible-hostname-completions (C-x @):先按 Ctrl + x,再按 @,等同于 Alt + @,主机名补全
  • dynamic-complete-history (M-Tab):尝试用 .bash_history 里面以前执行命令,进行补全
  • (M-*):在命令行一次性插入所有可能的补全
  • menu-complete:与 complete 相似,但是使用可能的补全列表中的某个匹配替换要补全的词。重复执行 menu-complete 将遍历可能的补全列表,插入每个匹配。到达补全列表的结尾时,鸣终端响铃(按照 bell-style 的设置来做)并恢复初始的文本。参数 n 将在匹配列表中向前移动 n 步;负数参数可以用于在列表中向后移动。这个命令应当与 TAB 键关联,但是默认情况下是没有关联的。
  • delete-char-or-list:删除光标下的字符,如果不是在行首或行尾(类似 delete-char)。如果在行尾,行为与 possible-completions 一致。 这个命令默认没有关联。
  • complete-into-braces (M-{):进行文件名补全,将可能的补全列表放在花括号中插入,使得列表可以被 shell 使用(参见上面的花括号扩展)。

注释:

  • 上面这些补全快捷键使用频率很低,有所了解即可

27.13. 宏

  • C-x (:开始保存输入字符为当前键盘宏
  • C-x ):停止保存输入字符为当前键盘宏,保存宏定义
  • C-x e:重新执行上次定义的键盘宏,即显示出宏中的字符,好像它们是从键盘输入的一样

注释:

  • 上面这些宏快捷键使用频率很低,有所了解即可

27.14. 杂项

  • re-read-init-file (C-x C-r):读入 inputrc 文件的内容,合并其中的按键关联和变量赋值
  • abort (C-g):取消当前编辑命令,鸣终端响铃(按照 bell-style 的设置来做)
  • do-uppercase-version (M-a, M-b, M-x, ...):如果有 Meta 前缀的字符 x 是小写的,那么与命令相关连的是对应的大写字符
  • prefix-meta (ESC):将输入的下一个字符加上 Meta 前缀。ESC f 等价于 Meta-f
  • undo (C-_, C-x C-u):增量的撤销,分别记住每一行
  • revert-line (M-r):撤销这一行的所有修改。这与执行命令 undo 足够多次的效果相同,将这一行恢复到初始状态
  • tilde-expand (M-&):对当前词进行波浪线扩展
  • set-mark (C-@, M-<space>):在 point 处设置 mark。如果给出了数值的参数,标记被设置到那个位置
  • exchange-point-and-mark (C-x C-x):交换 point 和 mark。当前光标位置被设置为保存的位置,旧光标位置被保存为 mark
  • character-search (C-]):读入一个字符,point 移动到这个字符下一次出现的地方。负数将搜索上一个出现
  • character-search-backward (M-C-]):读入一个字符,point 移动到这个字符上一次出现的地方。负数将搜索下面的出现
  • insert-comment (M-#):没有数值的参数时,逐行读取变量 comment-begin 的值将被插入到当前行首。如果给出一个数值的参数,命令的行为类似于一个开关:如果行首字符不匹配 comment-begin 的值,将插入这个值,否则匹配 comment-begin 的字符将被从行首删除。在两种情况下,这一行都被接受,好像输入了新行符一样。comment-begin 的默认值使得这个命令将当前行变成一条 shell 注释。如果数值参数使得注释字符被删除,这一行将被 shell 执行。
  • glob-complete-word (M-g):point 之前的词被当作路径扩展的一个模式,尾部暗含了一个星号。这个模式被用来为可能的补全产生匹配的文件名列表
  • glob-expand-word (C-x *):point 之前的词被当作路径扩展的一个模式,匹配的文件名的列表被插入,替换这个词。如果给出一个数值参数,在路径扩展之前将添加一个星号
  • glob-list-expansions (C-x g):显示 glob-expand-word 可能产生的扩展的列表,重绘当前行。如果给出一个数值参数,在路径扩展之前将添加一个星号
  • dump-functions:向逐行读取输出流打印所有的函数和它们的按键关联。如果给出一个数值参数,输出将被格式化,可以用作 inputrc 文件一部分
  • dump-variables:向逐行读取输出流打印所有可设置的逐行读取函数。如果给出一个数值参数,输出将被格式化,可以用作 inputrc 文件一部分
  • dump-macros:向逐行读取输出流打印所有关联到宏的逐行读取按键序列以及它们输出的字符串。如果给出一个数值参数,输出将被格式化,可以用作 inputrc 文件一部分
  • display-shell-version (C-x C-v):显示当前 bash 实例的版本信息

注释:

  • 上面这些宏快捷键使用频率很低,有所了解即可

27.15. 数值参数

  • digit-argument (M-0, M-1, ..., M--):将这个数字加入已有的参数中,或者开始新的参数。M-- 开始一个否定的参数。
  • universal-argument:这是指定参数的另一种方法。如果这个命令后面跟着一个或多个数字,可能还包含前导的负号,这些数字定义了参数。如果命令之后跟随着数字,再次执行 universal-argument 将结束数字参数,但是其他情况下被忽略。有一种特殊情况,如果命令之后紧接着 一个并非数字或负号的字符,下一命令的参数计数将乘以 4。参数计数初始是 1,因此第一次执行这个函数,使得参数计数为 4,第二次执行使得参数计数为 16,以此类推。

27.16. 可编程补全

当试图对一个命令的参数进行词的补全时,如果已经使用内建命令 complete 定义了这个命令的补全规则 ( compspec),将启动可编程补全功 能 (参见下面的 shell 内建命令(SHELL BUILTIN COMMANDS) 章节)。

28. 历史

当启用内建命令 set-o history 选项时,shell 允许访问命令历史,以前输入的命令的列表。HISTSIZE 的值用作命令列表中保存的命令数量。过去 HISTSIZE 个(默认为500)命令将被保存。shell 将每条命令在进行参数和变量扩展之前保存到历史列表中(参见上面的扩展章节),但是是在历史扩展进行之后,并且要经过 shell 变量 HISTIGNOREHISTCONTROL 处理。

在启动时,历史根据以变量 HISTFILE 的值为名的文件(默认是 ~/.bash_history)进行初始化。如果需要的话,以 HISTFILE 为名的文件将被截断,来包含不超过变量 HISTFILESIZE 的值指定的行数。当交互 shell 退出时,最后 $HISTSIZE 行被从历史列表中复制到 $HISTFILE 文件中。如果启用了 shell 选项 histappend(参见下面的内置命令章节中对内建命令 shopt 的描述),这些行被追加到历史文件中,否则历史文件被覆盖。如果 HISTFILE 被取消定义,或者如果历史文件不可写,历史将不会保存。保存历史之后,历史文件被截断,以包含不超过 HISTFILESIZE 行。如果 HISTFILESIZE 被取消定义,不会进行截断操作。

内建命令 fc(参见下面的内置命令章节)可以用来列出或修改并重新执行历史列表中的一部分。内建命令 history 可以用来显示或修改历史列表,操作历史文件。当使用命令行编辑时,每种编辑模式都有搜索命令,提供对历史列表的访问。

shell 允许控制哪些命令被保存到历史列表中。可以设置 HISTCONTROLHISTIGNORE 变量,来使得 shell 只保存输入命令的一个子集。shell 选项 cmdhist 如果被启用,将使得 shell 将多行的命令的每一行保存到同一个历史条目中,在需要的地方添加分号来保证语义的正确性。shell 选项 lithist 使得 shell 保存命令时,保留嵌入的新行而不是用分号代替。参见下面内置命令中,内建命令 shopt 的描述,有关设置和取消 shell 选项的信息。

29. 历史扩展

shell 支持历史扩展机制,类似于 csh 中历史扩展。这一节描述了可用的语法特征。在交互的 shell 中这一机制被默认启用,可以使用内建命令 set-H 选项来禁用它(参见下面的内置命令章节)。非交互的 shell 默认不进行历史扩展。

历史扩展将历史列表中的词引入输入流中,使得可以方便地重复已执行命令,在当前输入行中为前一个命令插入新的参数,或者快速修正前一个命令中的错误。

历史扩展在读入一整行后,在 shell 将它拆分成词之前立即进行。它由两部分组成。首先是判断替换中使用历史列表中哪一行。其次是选择那一行中要包含到当前行中的部分。从历史中选择的行称为 event,从那一行中选择的部分是 words。可以用多种多样的 modifiers 来操纵所选的词。在读入输入时,行被按照同样方式分解成词,因此多个以 metacharacter 分隔的词,如果被引号包含,就被当成一个词。历史扩展由历史扩展字符引入,默认是 !。只有反斜杠 () 和单引号可以引用历史扩展字符。

内建命令 shopt 可以设定多个选项值,来调整历史扩展的行为。如果 shell 选项 histverify 被启用(参见内置命令 shopt 的描述),并且正在使用逐行读取,历史替换不会被立即传给 shell 解释器。与此相对,扩展后的行被重新载入逐行读取编辑缓冲区,进行进一步的修改。如果正在使用逐行读取,并且启用了 shell 选项 histreedit,失败的历史替换将被重新载入到逐行读取编辑缓冲区,进行改正。内建命令 history-p 选项可以用来在执行之前查看历史扩展将如何进行。内置命令 history-s 选项可以用来在历史列表末尾添加命令,而不真正执行它们,从而 在接下来的调用中可以使用它们。

shell 允许控制历史扩展机制使用的多种字符(参见上面的 Shell 变量histchars 的描述)。

29.1. 事件指示器

事件指示器是一个对历史列表中某个命令行条目的引用。

  • ! 开始一个命令替换,除非后面跟随的是 blank, newline, = 或是 (
  • !n 引用命令行 n
  • !-n 引用当前命令行减去 n
  • !! 引用上一条命令。这是 !-1 的同义词
  • !string 引用最近的以 string 开始的命令
  • !?string[?] 引用最近的包含 string 的命令。尾部的 ? 可以被忽略,如果 string 之后紧接着一个新行符
  • ^string1^string2^ 快速替换。重复上一条命令,将 string1 替换为 string2!!:s/string1/string2/ 等价(参见下面的修饰符
  • !# 到此为止输入的整个命令行

29.2. 词指示器

词指示器用于从 event 中选择期望的词。: 分隔 event 规则与 word 指示器。它可以忽略,如果词指示器以 ^$*-% 开始。词被从行首开始编号,第一个词被表示为 0。插入当前行中的词以单个空格分隔。

  • 0 第 0 个词。对 shell 来将,这是命令名
  • n 第 n 个词
  • ^ 第一个参数。也就是,第 1 个词
  • $ 最后的参数
  • % 最近一次搜索 ?string? 匹配的词
  • x-y 一组词;-y0-y 的简写
  • * 所有词,除了第 0 个。这是 1-$ 的同义词。如果 event 中只有一个词,使用 * 也不是错误;这种情况下将返回空字符串
  • x* x-$ 的简写
  • x- -$ 的简写就像 x* 一样,但是忽略最后一个词

如果给出了一个 word 指示器,没有给出 event 规则,前一个命令将用作 event

29.3. 修饰符

可选的 word 指示器之后,可以出现一个或多个下述 modifiers 的序列,每一个都前缀有 :

  • h 删除文件名组成的尾部,只保留头部
  • t 删除文件名组成中前面的成分,保留尾部
  • r 删除 .xxx 形式中尾部的后缀成分,保留基本名称部分
  • e 删除所有内容,保留尾部的后缀
  • p 打印新的命令,但是不执行它
  • q 引用替换所得的词,使它不再进行替换
  • x 引用替换所得的词,类似与 q, 但是会根据 blanks,空白 和新行符分解为词。
  • s/old/new/ 将事件行中出现的第一个 old 替换为 new。任何分隔符都可以用来代替 /,最后一个分隔符是可选的,如果它是事件行的最后一个字符。old 和 new 中的分隔符可以用一个反斜杠来引用。如果 & 出现在 new 中,它将替换为 old。可以用单个反斜杠来引用 &。如果 old 为空,它将设置为最后替换的 old,或者,如果前面没有发生过历史替换,就是 !?string[?] 搜索中的最后一个 string。
  • & 重复上一次替换
  • g 使得改变被整个事件行所接受。用于与 :s' 或:&' 结合 (例如,:gs/old/new/')。 如果与:s' 结合使用,任何分隔符都可以用 来代替 /, 最后一个分隔符是可选的,如果它是事件行的最后一个字符。

30. 内置命令

30.1. echo

用法:echo [-neE] [参数 ...]

功能:将参数写到标准输出(默认是控制台)。

在标准输出上,显示用空格分割的参数,最后的参数跟一个换行。

选项:

  • -n 不要追加换行
  • -e 启用反斜杠转义
  • -E 显式地抑制对于反斜杠转义的解释

echo 识别的反斜杠转义序列,跟上文引用章节中反斜杠转义序列基本相同,不同之处在于,echo 不识别下面的转义序列:

  • \'
  • \"
  • \?
  • \cx
  • \nnn echo 识别一个类似的转义序列:\0nnn 表示一个 ASCII 字符,值是八进制值 nnn(零到三个八进制数)

注释:

  • -e-E 互为反操作,-e 支持识别 \ 转义,-E 关闭 \ 转义,即使 xpg_echoon
  • xpg_echo 默认为 off,因此,echo 默认不识别 \ 转义
  • 在控制台分别输入下面三个命令,比较他们的运行效果
  • bash -O xpg_echo -c "echo 'hello\nworld'"
  • bash +O xpg_echo -c "echo 'hello\nworld'"
  • bash -O xpg_echo -c "echo -E 'hello\nworld'"
  • -e 识别反斜杠转义,在控制台分别输入下面两个命令,比较他们的运行效果
  • echo -e "hello\nworld"
  • echo "hello\nworld"
  • -n 取消行尾换行符,在控制台分别输入下面两个命令,比较他们的运行效果
  • echo hello; echo world
  • echo -n hello; echo world
  • echo 不支持的转义序列,在控制台逐行输入下面命令,观察他他们的运行效果
echo $'\''
echo -e "\'"

echo $'\"'
echo -e '\"'

echo $'\?'
echo -e "\?"

echo $'\150\145\154\154\157\40\167\157\162\154\144'
echo -e '\150\145\154\154\157\40\167\157\162\154\144'
echo -e '\0150\0145\0154\0154\0157\040\0167\0157\0162\0154\0144'

30.2. history

格式:

history [-c] [-d 偏移量] [n]
history -anrw [文件名]
history -ps 参数 [参数...]

功能:显示或操纵历史列表。

带行号显示历史列表,将每个被修改的条目加上 * 前缀。参数 n 会仅列出最后的 n 个条目。

选项:

  • -c 删除所有条目从而清空历史列表
  • -d 偏移量从指定位置删除历史列表。负偏移量将从历史条目末尾开始计数
  • -a 将当前会话的历史行追加到历史文件中
  • -n 从历史文件中读取所有未被读取的行并且将它们附加到历史列表
  • -r 读取历史文件并将内容追加到历史列表中
  • -w 将当前历史写入到历史文件中
  • -p 对每一个 ARG 参数展开历史并显示结果,而不存储到历史列表中
  • -s 以单条记录追加 ARG 到历史列表中

如果给定了 FILENAME 文件名,则它将被作为历史文件。否则如果 $HISTFILE 变量有值的话使用之,不然使用 ~/.bash_history 文件。

如果 $HISTTIMEFORMAT 变量被设定并且不为空,它的值会被用于 strftime(3) 的格式字符串来打印与每一个显示的历史条目想关联的时间戳,否则不打印时间戳。

注释:

  • echo $HISTFILE,查看命令历史保存的文件,这个文件可以编辑
  • history | less,查看命令历史,每条历史命令一行,每行都有编号
  • !n,n 是编号,会调用第 n 个编号的历史命令,一般不这样使用
  • !!,调用历史命令中的最后一条命令,一般不这样使用,c-p 不香吗
  • history | grep keyword,检索命令历史
  • HISTTIMEFORMAT='%F %T '; history,命令历史输出第一列是编号,第二列是该命令执行的时间,第三列是命令
  • history -c 这个命令慎用,会把所有命令历史删除

30.3. let

格式:let 参数 [参数 ...]

功能:估值算术表达式。

每个参数都是要求值的算术表达式(参见算术求值章节)。如果最后一个参数 arg 求值结果是 0,let 返回 1;否则返回 0。

30.4. set

用法:set [--abefhkmnptuvxBCHP] [-o 选项名] [--] [参数 ...]

功能:设定或取消设定 shell 选项和位置参数的值。

改变 shell 选项和位置参数的值,或者显示 shell 变量的名称和值。

选项:

  • -a 标记修改的或者创建的变量为导出
  • -b 立即通告任务终结
  • -e 如果一个命令以非零状态退出,则立即退出
  • -f 禁用文件名生成(模式匹配)
  • -h 当查询命令时记住它们的位置
  • -k 所有的赋值参数被放在命令的环境中,而不仅仅是命令名称之前的参数
  • -m 启用任务控制
  • -n 读取命令但不执行
  • -o 选项名

    • 设定与选项名对应的变量:
    • allexport 与 -a 相同
    • braceexpand 与 -B 相同
    • emacs 使用 emacs 风格的行编辑界面
    • errexit 与 -e 相同
    • errtrace 与 -E 相同
    • functrace 与 -T 相同
    • hashall 与 -h 相同
    • histexpand 与 -H 相同
    • history 启用命令历史
    • ignoreeof shell 读取文件结束符时不会退出
    • interactive-comments 允许在交互式命令中显示注释
    • keyword 与 -k 相同
    • monitor 与 -m 相同
    • noclobber 与 -C 相同
    • noexec 与 -n 相同
    • noglob 与 -f 相同
    • nolog 目前可接受但是被忽略
    • notify 与 -b 相同
    • nounset 与 -u 相同
    • onecmd 与 -t 相同
    • physical 与 -P 相同
    • pipefail 管道的返回值是最后一个非零返回值的命令的返回结果,或者当所有命令都返回零是也为零
    • posix 改变默认时和 Posix 标准不同的 bash 行为以匹配标准
    • privileged 与 -p 相同
    • verbose 与 -v 相同
    • vi 使用 vi 风格的行编辑界面
    • xtrace 与 -x 相同
  • -p 无论何时当真实的有效的用户身份不匹配时打开禁用对 $ENV 文件的处理以及导入 shell 函数。关闭此选项会导致有效的用户编号和组编号设定为真实的用户编号和组编号

  • -t 读取并执行一个命令之后退出
  • -u 替换时将为设定的变量当作错误对待
  • -v 读取 shell 输入行时将它们打印
  • -x 执行命令时打印它们以及参数
  • -B shell 将执行花括号扩展
  • -C 设定之后禁止以重定向输出的方式覆盖常规文件
  • -E 设定之后 ERR 陷阱会被 shell 函数继承
  • -H 启用 ! 风格的历史替换。当 shell 是交互式的时候这个标识位默认打开
  • -P 设定之后类似 cd 的会改变当前目录的命令不追踪符号链接
  • -T 设定之后 DEBUG 陷阱会被 shell 函数继承
  • -- 任何剩余的参数会被赋值给位置参数。如果没有剩余的参数,位置参数不会被设置。
  • - 任何剩余的参数会被赋值给位置参数。-x-v 选项已关闭

使用 + 而不是 - 会使标志位被关闭。标志位也可以在 shell 被启动时使用。当前的标志位设定可以在 $- 变量中找到。剩余的参数是位置参数并且是按照 $1$2,.. $n 的顺序被赋值的。如果没有给定参数,则打印所有的 shell 变量。

30.5. test

用法:test expr

[ expr ]

返回状态值 0 或 1,根据条件表达式 expr 的求值而定。每个操作符和操作数都必须是一个单独的参数。表达式使用上面条件表达式中的操作构造。

表达式可以用下列操作符结合,以优先级的降序列出。

  • ! expr 值为真,如果 expr 为假。
  • ( expr ) 返回 expr 的值。括号可以用来超越操作符的一般优先级。
  • expr1 -a expr2 值为真,如果 expr1 和 expr2 都为真。
  • expr1 -o expr2 值为真,如果 expr1 或 expr2 为真。

test 和 [ 使用基于参数个数的一系列规则,对条件表达式进行求值。

  • 0 arguments 表达式为假。
  • 1 argument 表达式为真,当且仅当参数非空。
  • 2 arguments 如果第一个参数是 !,表达式为真,当且仅当第二个参数为空。如果第一个参数是上面条件表达式中列出的单目条件运算符之一,表达式为真,当且仅当单目测试为真。如果第一个参数不是合法的单目条件运算符,表达式为假。
  • 3 arguments 如果第二个参数是上面条件表达式中列出的双目条件操作符之一,表达式的结果是使用第一和第三个参数作为操作数的二进制测试的结果。如果第一个参数是 !,表达式值是使用第二和第三个参数进行双参数测试的结果取反。如果第一个参数是 (,第三个参数是 ),结果是对第二个参数进行单参数测试的结果。否则,表达式为假。这种情况下 -a 和 -o 操作符被认为二进制操作符。
  • 4 arguments 如果第一个参数是 !,结果是由剩余参数组成的三参数表达式结果取反。否则,表达式被根据上面列出的优先级规则解释并执行。
  • 5 或更多 arguments 表达式被根据上面列出的优先级规则解释并执行。

30.6. times

times 对 shell 以及 shell 运行的进程,打印累计的用户和系统时间。返回状态是 0。

30.7. type

用法:type [-afptP] 名称 [名称 ...]

功能:显示命令的类型信息。

对于每一个名称,如果作为命令,它将如何被解释。

选项:

  • -a 显示所有包含名称为 NAME 的可执行文件的位置;包括别名、内建和函数。仅当 -p 选项没有使用时
  • -f 抑制 shell 函数查询
  • -P 为每个 NAME 启动 PATH 路径搜索,即使它是别名、内建或函数,并且返回将被执行的磁盘上文件的名称
  • -p 返回命令在磁盘上的文件路径,当 type -t NAME 不返回 file 时,不返回任何值
  • -t 返回下列词中的任何一个 aliaskeywordfunctionbuiltinfile 或者空,相应地如果 NAME 是一个别名、shell 保留字、shell 函数、shell 内置命令、磁盘文件或没有找到

参数:

  • 名称 将要解析的命令

注释:

  • type 英文单词的意思就是类型,用来获取命令的类型信息
  • 命令的类型有哪些?别名、关键字、函数、内置命令、磁盘上的可执行文件或者空(命令没有找到)
  • 控制台逐行输入下面命令,观察运行效果,理解各个选项的作用

    type echo
    type -a echo
    type -t echo
    type -p echo
    type -P echo
    
    type apt
    type -p apt
    type -t apt
    
    type ls if echo apt abc
    type -t ls if echo apt abc
    
  • 注意:abc 是个不存在的命令

  • ~/.bashrc 脚本最后一行下面添加一个 xyz 函数定义,内容如下
    xyz () {
      echo This is xyz function.
    }
    
  • 控制台输入下面命令,观察运行效果
    bash
    type xyz
    type -f xyz
    type -t xyz
    exit
    

31. 受限的 shell

2017 -  by 王顶. All rights reserved.本站访客人数  人次

results matching ""

    No results matching ""