shell脚本系列:3、shell命令

shell脚本系列:3、shell命令



一个简单的shell命令,如echo a b c,由命令本身后跟以空格分隔的参数组成。

更复杂的shell命令由以各种方式排列在一起的简单命令组成:在一个管道中,一个命令的输出成为另一个命令的输入,在一个循环或条件构造中,或在一些其他分组中。

Reserved Words对shell有特殊意义的词语
Simple Commands最常见的命令类型
Pipelines连接几个命令的输入和输出。
Lists如何顺序执行命令。
Compound Commands控制流的Shell命令。
Coprocesses命令之间的双向通信。
GNU Parallel并行运行命令。

1. 保留字

保留字是对shell具有特殊意义的字。它们用于shell复合命令的开始和结束。

当命令的第一个单词不带引号时,以下单词被识别为保留词(请参阅下面的例外情况):

ifthenelifelsefitime
forinuntilwhiledodone
caseesaccoprocselectfunction
{}[[]]!

如果incaseselect命令的第三个单词,则它被识别为保留字。如果indofor命令中的第三个单词,则它们被识别为保留字。

2. 简单的命令

简单命令是最常遇到的一种命令。它只是一个由空格分隔的单词序列,由shell的一个控制操作符结束(参见定义)。第一个字通常指定要执行的命令,其余字是该命令的参数。

简单命令的返回状态(参见Exit status)是POSIX 1003.1 waitpid函数提供的退出状态,如果命令以信号n终止,则返回128+n。

3. 管道

管道是一个由控制操作符’ | ‘或’ |& '分隔的一个或多个命令序列。

管道的格式是:

[time [-p]] [!] command1 [ | or |& command2 ]

管道中每个命令的输出通过管道连接到下一个命令的输入。也就是说,每个命令读取前一个命令的输出。此连接将在该命令指定的任何重定向之前执行。

如果使用了’ |& ',command1的标准错误,除了它的标准输出外,通过管道连接到command2的标准输入;它是2>& 1|的缩写。将标准错误隐式重定向到标准输出是在命令指定的任何重定向之后执行的。

保留字time会导致管道完成后打印计时统计信息。当前的统计信息包括执行命令所消耗的时间(墙壁时钟)以及用户和系统时间。-p选项将输出格式更改为POSIX指定的格式。当shell处于POSIX模式时(参见Bash POSIX模式),如果下一个令牌以’ - '开头,它不会将时间识别为保留字。可以将TIMEFORMAT变量设置为指定如何显示定时信息的格式字符串。有关可用格式的描述,请参见Bash变量。使用time作为保留字允许对shell内置函数、shell函数和管道计时。外部时间命令不能很容易地计算这些时间。

当shell处于POSIX模式时(请参阅Bash POSIX模式),时间后面可能会有一个换行符。在本例中,shell显示shell及其子程序所消耗的总用户和系统时间。TIMEFORMAT变量可用于指定时间信息的格式。

如果管道不是异步执行的(参见列表),shell将等待管道中的所有命令完成。

管道中的每个命令都在它自己的子shell中执行,这是一个单独的进程(请参阅命令执行环境)。如果使用shopt builtin启用了lastpipe选项(请参阅shopt builtin),那么管道的最后一个元素可能由shell进程运行。

管道的退出状态是管道中最后一个命令的退出状态,除非启用了管道失败选项(请参见设置内置)。如果启用了pipefail,管道的返回状态是最后一个(最右边的)以非零状态退出的命令的值,如果所有命令都成功退出,则返回0。如果保留了单词’ !’,退出状态是上述退出状态的逻辑否定。shell将等待管道中的所有命令在返回值之前终止。

4. 命令列表

列表是一个由一个或多个管道组成的序列,管道之间由一个操作符’;’、’ & ‘、’ && ‘或’ || ‘分隔,并可选以’;’、’ & '或换行符之一结束。

在这些列表操作符中,’ && ‘和’ || ‘具有相同的优先级,’;‘和’ & '具有相同的优先级。

列表中可以出现一个或多个换行符序列,用于分隔命令,相当于分号。

如果一个命令被控制操作符&, shell在子shell中异步执行该命令。这被称为在后台执行命令,这些被称为异步命令。shell不会等待命令完成,返回状态为0 (true)。当作业控制不活动时(请参阅作业控制),在没有任何显式重定向的情况下,异步命令的标准输入将从/dev/null重定向。

以’;'分隔的命令按顺序执行;shell等待每个命令依次终止。返回状态为上次执行命令的退出状态。

AND和OR列表是由控制操作符’ && ‘和’ || '分隔的一个或多个管道序列。AND和OR链表是用左结合律执行的。

AND列表具有这种形式

command1 && command2

当且仅当command1返回退出状态为零(成功)时,就执行Command2。

OR列表具有这种形式

command1 || command2

当且仅当command1返回一个非零退出状态时,就执行Command2。

AND和OR列表的返回状态是列表中最后执行的命令的退出状态。

5. 复合循环指令

Looping Constructs用于迭代操作的Shell命令。
Conditional Constructs用于条件执行的Shell命令。
Command Grouping对命令进行分组的方法。

复合命令是shell编程语言构造。每个结构以一个保留字或控制操作符开始,并以相应的保留字或操作符结束。与复合命令关联的任何重定向(请参阅重定向)都应用于该复合命令中的所有命令,除非显式覆盖。

在大多数情况下,复合命令描述中的命令列表可以用一个或多个换行符与命令的其余部分分隔,并在后面用换行符代替分号。

Bash提供了循环结构、条件命令和将命令分组并作为一个单元执行的机制。

5.1 循环结构

Bash支持以下循环结构。

请注意,在命令语法的描述中,只要出现’;’,就可以用一个或多个换行符替换。

until

until命令的语法如下:

until test-commands; do consequent-commands; done

​ 只要test-commands的退出状态不为零,就执行结果命令。返回状态是结果命令中最后执行的命令的退出状态,如果没有执行任何命令则返回零

while

while命令的语法如下:

while test-commands; do consequent-commands; done

​ 只要test-commands的退出状态为零,就执行结果命令。返回状态是结果命令中最后执行的命令的退出状态,如果没有执行任何命令则返回零。

for

for命令的语法如下:

for name [ [in [words …] ] ; ] do commands; done

展开单词(请参阅Shell expansion),并对结果列表中的每个成员执行一次命令,将名称绑定到当前成员。如果’ in words ‘没有出现,for命令对设置的每个位置参数执行一次命令,就好像’ $@ ’ '中已经指定了一样(参见特殊参数)。

返回状态是最后一个执行命令的退出状态。如果扩展词中没有项,则不执行任何命令,返回状态为零。

for命令的另一种形式也被支持:

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

首先,根据下面描述的规则对算术表达式expr1求值(请参阅Shell算术)。然后重复计算算术表达式expr2,直到其计算结果为零。每次expr2计算为非零值时,执行命令并计算算术表达式expr3。如果省略了任何表达式,它的行为就好像它的计算结果是1。返回值是所执行命令中最后一个命令的退出状态,如果任何表达式无效则返回false。

break和continue builtins(见Bourne Shell builtins)可以用来控制循环的执行。

5.2 条件结构

if

if命令的语法如下:

if test-commands; then
  consequent-commands;
[elif more-test-commands; then
  more-consequents;]
[else alternate-consequents;]
fi

执行测试命令列表,如果返回状态为零,则执行结果命令列表。如果test-commands返回一个非零状态,则依次执行每个elif列表,如果其退出状态为零,则执行相应的more- consequences,命令完成。如果存在’ else alternate- consequences ',并且final If或elif子句中的final命令的退出状态非零,则执行alternate- consequences。返回状态是最后执行的命令的退出状态,如果测试条件不为true,则返回零。

case

case 命令的语法如下:

case word in
    [ [(] pattern [| pattern]) command-list ;;]…
esac

Case将选择性地执行与第一个匹配单词的模式对应的命令列表。根据模式匹配中描述的规则执行匹配。如果启用了nocasematch shell选项(请参阅the shopt Builtin中对shopt的描述),则执行匹配而不考虑字母字符的大小写。’ | ‘用于分隔多个模式,’)'操作符终止模式列表。模式列表和相关的命令列表称为子句。

每个子句必须以“;;”、“;&”或“;;&”结尾。在尝试匹配之前,该词将经历波浪号展开、参数展开、命令替换、算术展开和引号删除(请参阅Shell参数展开)。每个模式都经历了波浪号展开、参数展开、命令替换和算术展开。

可以有任意数量的大小写子句,每个子句以’;;’、’;& ‘或’;;& ‘结尾。第一个匹配的模式确定要执行的命令列表。使用’ * '作为定义默认情况的最终模式是一个常见的习惯用法,因为该模式总是匹配的。

下面是一个脚本中的示例用例,可以用来描述动物的一个有趣特性:

echo -n "Enter the name of an animal: "
read ANIMAL
echo -n "The $ANIMAL has "
case $ANIMAL in
  horse | dog | cat) echo -n "four";;
  man | kangaroo ) echo -n "two";;
  *) echo -n "an unknown number of";;
esac
echo " legs."

如果使用了’;;‘操作符,则在第一个模式匹配之后不会尝试后续匹配。使用’;& ‘代替’;;‘将导致执行继续与下一个子句关联的命令列表(如果有的话)。使用’;;& ‘代替’;;'将导致shell在下一个子句中测试模式(如果有的话),并在成功匹配时执行任何关联的命令列表,继续执行case语句,就像模式列表没有匹配一样。

如果没有匹配模式,则返回状态为零。否则,返回状态为已执行命令列表的退出状态。

select

select结构允许简单地生成菜单。它的语法几乎与for命令相同:

select name [in words …]; do commands; done

扩展in后面的单词列表,生成项列表。展开的单词集被打印在标准错误输出流上,每个单词前面都有一个数字。如果省略in,则打印位置参数,就像指定了in "$@"一样。然后显示PS3提示符,并从标准输入中读取一行。如果该行包含一个数字,对应于显示的单词之一,那么name的值将被设置为该单词。如果该行为空,则再次显示单词和提示符。如果读取了EOF,则完成select命令。任何其他读值都会导致name被设置为null。读取的行保存在变量REPLY中。

这些命令在每次选择之后执行,直到执行break命令,此时select命令完成。

下面是一个示例,它允许用户从当前目录中选择一个文件名,并显示所选文件的名称和索引。

select fname in *;
do
	echo you picked $fname \($REPLY\)
	break;
done
((…))

(( expression ))

算术表达式根据下面描述的规则进行计算(请参阅Shell算术)。如果表达式的值不为零,则返回状态为0;否则返回状态为1。这就等于

let "expression"

请参阅Bash Builtins,了解letbuiltin的完整描述。

[[]]

[[ expression ]]

根据条件表达式expression的计算结果返回0或1的状态。表达式由下面Bash条件表达式中描述的基本表达式组成。不对[[and]]之间的单词进行分词和文件名展开;执行波浪号展开、参数和变量展开、算术展开、命令替换、进程替换和报价删除。条件运算符如’ -f '必须去掉引号才能被识别为基本运算符。

[[一起使用时,’ < ‘和’ > '操作符使用当前区域设置按字典顺序排序。

当使用和!=操作符时,操作符右边的字符串被认为是一个模式,并根据下面pattern Matching中描述的规则进行匹配,就像启用了extglob shell选项一样。=操作符与相同。如果启用了nocasematch shell选项(请参阅the shopt Builtin中对shopt的描述),则执行匹配而不考虑字母字符的大小写。如果字符串匹配(==)或不匹配(!=),则返回值为0,否则返回值为1。模式的任何部分都可以用引号括起来,以强制引号括起来的部分作为字符串匹配。

还有一个附加的二元运算符’=~’,其优先级与’==‘和’!='相同。当使用它时,操作符右边的字符串被认为是POSIX扩展正则表达式并进行相应的匹配(使用POSIX regcomp和regexec接口,通常在regex(3)中描述)。如果字符串匹配该模式,则返回值为0,否则返回值为1。如果正则表达式语法不正确,条件表达式的返回值是2。如果启用了nocasematch shell选项(请参阅the shopt Builtin中对shopt的描述),则执行匹配而不考虑字母字符的大小写。模式的任何部分都可以用引号括起来,以强制引号括起来的部分作为字符串匹配。必须小心处理正则表达式中的括号表达式,因为正常的引号字符在括号之间失去了它们的意义。如果模式存储在一个shell变量中,引用变量展开强制将整个模式匹配为字符串。

如果匹配字符串的任何部分,模式将匹配。使用’^‘和’$'正则表达式操作符锚定模式,强制它匹配整个字符串。数组变量BASH_REMATCH记录字符串的哪些部分与模式匹配。索引为0的BASH_REMATCH元素包含匹配整个正则表达式的字符串部分。正则表达式中带圆括号的子表达式匹配的子字符串将保存在其余的BASH_REMATCH索引中。索引为n的BASH_REMATCH元素是字符串中与第n个带括号的子表达式匹配的部分。

例如,下面的代码将匹配一行(存储在shell变量行中),如果在空格字符类中有一个由任意数字(包括零)组成的字符序列,0个或一个’ a '实例,然后是a ’ b '实例:

[[ $line =~ [[:space:]]*(a)?b ]]

这意味着像’ aab ‘和’ aaaaaab ‘这样的值将会匹配,包含’ b '的行在其值的任何地方也会匹配。

将正则表达式存储在shell变量中通常是避免使用引号字符(对shell来说是特殊的)问题的有效方法。有时候,不使用引号就直接指定正则表达式是困难的,或者在注意删除shell中的引号时跟踪正则表达式使用的引号也是困难的。使用shell变量来存储模式可以减少这些问题。例如,下面的表达式等价于上面的表达式:

pattern='[[:space:]]*(a)?b'
[[ $line =~ $pattern ]]

如果您想匹配一个正则表达式语法中的特殊字符,则必须用引号将其去掉特殊含义。这意味着在模式’ xxx.txt ‘中,’.’ 匹配字符串中的任何字符(它通常的正则表达式含义),但在模式’ "xxx.txt"中,它只能匹配字面的’ . '。Shell程序员应该特别注意反斜杠,因为Shell和正则表达式都使用反斜杠来删除下面字符的特殊含义。以下两组命令是不相等的:

pattern='\.'

[[ . =~ $pattern ]]
[[ . =~ \. ]]

[[ . =~ "$pattern" ]]
[[ . =~ '\.' ]]

前两个匹配将成功,但后两个不成功,因为在后两个中反斜杠将是要匹配的模式的一部分。在前两个示例中,反斜杠删除了’.‘中的特殊含义。所以是字面上的’.’。如果第一个例子中的字符串不是’.’,比如’ a ‘,模式就不匹配了,因为引号里的’.'就失去了匹配任何单个字符的特殊意义。

表达式可以使用以下操作符组合,按优先级递减排列:

  • ( expression )

    返回表达式的值。这可用于重写操作符的正常优先级。

  • ! expression

    如果表达式为假则为真。

  • expression1 && expression2

    如果expression1和expression2都为真,则为真。

  • expression1 || expression2

    如果expression1或expression2为真,则为真。

如果expression1的值足以确定整个条件表达式的返回值,那么&&和||操作符不会计算expression2。

5.3 分组命令

Bash提供了两种方法来对要作为一个单元执行的命令列表进行分组。当命令分组时,重定向可能应用到整个命令列表。例如,列表中所有命令的输出可以重定向到单个流。

()

( list )

将命令列表放在圆括号之间会创建一个子shell环境(请参阅命令执行环境),列表中的每个命令都将在该子shell中执行。由于列表是在子shell中执行的,所以在子shell完成后,变量赋值将不再有效。

{}

{ list; }

将一个命令列表放在花括号之间会导致在当前shell上下文中执行该列表。没有创建子shell。列表后面的分号(或换行符)是必需的。

除了创建子shell之外,由于历史原因,这两种构造之间还有一些细微的区别。大括号是保留字,因此它们必须用空格或其他shell元字符与列表分隔。圆括号是操作符,即使没有用空格将它们与列表分隔开,也会被shell识别为单独的标记。

这两个构造的退出状态都是list的退出状态。

6. 协程

coprocess是一个shell命令,前面加上coproc保留字。在子shell中异步执行coprocess,就像命令被’ & '控制操作符终止一样,在执行shell和coprocess之间建立了一个双向管道。

协进程的格式是:

coproc [NAME] command [redirections]

这将创建一个名为NAME的协进程。如果没有提供NAME,则默认名称为COPROC。如果command是简单命令,则不能提供NAME(参见简单命令);否则,它将被解释为简单命令的第一个单词。

当执行coprocess时,shell会在执行shell的上下文中创建一个名为NAME的数组变量(请参阅Arrays)。命令的标准输出通过管道连接到执行shell中的文件描述符,该文件描述符被分配给NAME[0]。命令的标准输入通过管道连接到执行shell中的文件描述符,该文件描述符被分配给NAME[1]。此管道是在命令指定的任何重定向之前建立的(参见重定向)。文件描述符可以作为shell命令和使用标准字展开的重定向的参数。除了为执行命令和进程替换而创建的文件描述符外,子shell中没有文件描述符。

执行协进程派生的shell的进程ID可以作为变量NAME_PID的值使用。wait builtin命令可以用来等待协进程终止。

因为coprocess是作为异步命令创建的,所以coproc命令总是返回成功。协进程的返回状态为命令的退出状态。

7. GUN Parallel

有一些方法可以并行运行没有内置到Bash中的命令。GNU Parallel就是这样一个工具。

顾名思义,GNU Parallel可用于并行地构建和运行命令。您可以使用不同的参数运行相同的命令,无论它们是文件名、用户名、主机名还是从文件中读取的行。GNU Parallel提供了许多最常见操作的简写引用(输入行、输入行的各个部分、指定输入源的不同方式,等等)。Parallel可以将xargs或输入源中的命令替换为几个不同的Bash实例。

要获得完整的描述,请参考GNU Parallel文档。通过几个例子可以简要介绍它的用法。

例如,很容易将当前目录及其子目录中的所有html文件替换为gzip:

find . -type f -name '*.html' -print | parallel gzip

如果需要保护文件名中的特殊字符,如换行符,请使用find的-print0选项和parallel的-0选项。

当文件的数量太大,无法用一个mv调用来处理时,你可以使用Parallel从当前目录移动文件:

printf '%s\n' * | parallel mv {} destdir

如您所见,{}被替换为从标准输入中读取的每一行。虽然使用ls可以在大多数情况下工作,但它不足以处理所有文件名。printf是一个内置的shell,因此不受内核对程序参数数量的限制,所以您可以使用’ * '(但请参阅下面关于dotglob shell选项的介绍)。如果需要在文件名中容纳特殊字符,可以使用

printf '%s\0' * | parallel -0 mv {} destdir

如上所述。

这将运行与当前目录中有多少文件一样多的mv命令。你可以通过添加-X选项来模拟并行xargs:

printf '%s\0' * | parallel -0 -X mv {} destdir

(如果启用了dotglob选项,则可能必须修改模式。)

GNU Parallel可以替代某些对从文件中读取的行进行操作的常见习惯用法(在本例中,每行列出一个文件名):

while IFS= read -r x; do
		do-something1 "$x" "config-$x"
		do-something2 < "$x"
	done < file | process-output

使用更紧凑的lambdas语法:

cat list | parallel "do-something1 {} config-{} ; do-something2 < {}" |
           process-output

Parallel提供了一个内置机制来删除文件名扩展名,这有助于批处理文件转换或重命名:

ls *.gz | parallel -j+0 "zcat {} | bzip2 >{.}.bz2 && rm {}"

这将使用bzip2重新压缩当前目录中以.gz结尾的所有文件,每个CPU并行运行一个作业(-j+0)。(这里我们用ls表示简洁;如上所述使用find在面对包含意外字符的文件名时更加健壮。)Parallel可以从命令行获取参数;以上也可以写成:

parallel "zcat {} | bzip2 >{.}.bz2 && rm {}" ::: *.gz

如果命令产生输出,您可能希望在输出中保持输入顺序。例如,以下命令:

{
    echo foss.org.my ;
    echo debian.org ;
    echo freenetproject.org ;
} | parallel traceroute

将在输出中显示最先完成的traceroute调用。添加-k选项:

{
    echo foss.org.my ;
    echo debian.org ;
    echo freenetproject.org ;
} | parallel -k traceroute

将确保首先显示traceroute foss.org.my的输出。

最后,Parallel可以用来并行运行一系列shell命令,类似于’ cat file | bash '。获取一个文件名列表,创建一系列shell命令来对它们进行操作,并将该命令列表提供给shell,这种情况并不少见。Parallel可以加快速度。假设该文件包含shell命令列表,每行一个,

parallel -j 10 < file

将使用shell计算命令(因为没有显式的命令作为参数提供),每次以十个shell作业为块。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

昵称系统有问题

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值