命令行格式

不同的命令可接受的命令行格式或有不同,一般情况下,一个标准的命令行格式为如下所列:

1
conmmand-name options argement

从技术上看,shell会依据 IFS(Internet Field Seperator) 将命令行所输入的文字拆解成“字段”(word)。当遇到 CR (Carriage Return) 键时,执行语句。

其中的 IFS 是 shell 预设使用的分隔符,可以由一个或多个如下符号组成:

  • 空格符(White Space)
  • 制表符(Tab)
  • 回车符(Enter)

系统可接受的命令名称(command-name)可以从如下途径获得:

  • 明确路径所指定的外部命令
  • 命令别名(alias)
  • 自定义的函数(function)
  • shell内建命令(built-in)
  • $PATH 之下的外部命令

可以使用 type 命令来区分一个命令是shell内建的命令还是外部命令。

每一个命令行均必需包含命令名,这是不可或缺的。

特殊符号(meta)

command-line 的每一个字符分成以下两种:

  • literal:也就是普通纯文本(字面值),对 shell 来说没特殊功能;
  • meta:具有特定功能的字符保留单元。IFSCR 就是这类字符的典型代表。

除了 IFSCR,常用的 meta 有:

meta 用途
= 设定变量
$ 作变量或运算替换(请不要与 shell prompt 搞混了)
> 重定向 stdout
< 重定向 stdin
| 管道
& 重定向文件描述符,或将命令置于后台执行。
() 将其内的命令置于嵌套子shell中执行,或用于运算或命令替换。
{} 将其内的命令置于匿名函数中执行,或用在变量替换的界定范围。
[] 做判断,相当于 test 命令。
; 在前一个命令执行结束时,忽略其返回值。继续执行下一个命令。
! 执行 history 列表中的命令。

关闭 meta

假如我们需要在 command-line 中将这些保留字元的功能关闭的话,就需要使用引号来处理了。

在 bash 中,常用的 quoting 有如下三种方法:

  • hard quote:' '(单引号),凡在 hard quote 中的所有 meta 均被关闭。
  • soft quote:" "(双引号),在 soft quote 中部分 meta 都会被关闭,但某些刚保留(如 $)。
  • escape:\(反斜线),只有紧接在 escape (逃逸字符)之后的 单一 meta 才被关闭。

示例

示例1 空格键未被关闭

下面的例子中,空格键未被关闭,作为 IFS 处理,导致了未找到命令的错误:

1
2
3
$ A=B C # 空格键未被关闭
Bash C: 未找到命令
$ echo $A

示例2 空格键被引号关闭

下面的例子演示了用 soft quote 关闭空格键,此时空格键不再作为 IFS,而是普通的空格符。

1
2
3
$ A="B C" # 空格键被引号关闭
$ echo A
B C

示例3 Enter键被引号关闭

下面的例子演示了用 hard quote 关闭回车键,此时回车键不再作为 CR,而是普通的换行符。后面会演示 hard quote 和 soft quote 的区别。

1
2
3
4
5
6
$ A='B # 回车键也可以被引号关闭
> C
>'
$ echo $A
B
C

示例4 空格键

下面的例子演示了用 反斜杆 作为逃逸字符关闭后面紧跟的回车键。此时回车键不再作为 CR,而是普通的换行符。

1
2
3
4
5
$ A=B\ # 回车键被escape关闭
>C
>
$ echo $A
BC

soft quote vs. hard quote

soft quote 和 hard quote 的不同主要是对于某些 meta 的关闭与否。以 $ 来做说明:

1
2
3
4
5
$ A=B\ C
$ echo "$A"
B C
$ echo '$A'
$A

区分 shell meta 与 command meta

前面我们提到的那些 meta ,都是在 command line 中有特殊用途的。但有时候我们需要让这些 meta 不直接作为整条语句的 meta (shell meta) ,而是作为这条语句内部某个命令的 meta(command meta),这时就需要使用上面的手段来将这些 meta 关闭,让它作为该命令的 meta 而不是整条语句的 meta。

例如,{} 是将其一系列 command line 置于匿名函数中执行(可简单视为 command block),但是 awk 却需要用 {} 来区分出 awk 的命令区段(BEGIN、MAIN、END)。如果在 shell 中输入:

1
$ awk {print $0} 1.txt

由于 {} 在 shell 中并没有被关闭,于是 shell 就将 {print $0} 视为代码块,但后面又没有;符号作为命令分隔符,因此就出现语法错误结果。

要解决之,可以用下面三种关闭方法任一种:

1
2
3
$ awk '{print $0}' 1.txt
$ awk "{print \$0}" 1.txt
$ awk \{print\ \$0\} 1.txt

这样,{print $0} 完整的成为了 awk 参数中的 command meta。

shell 中的各种括号

():一个圆括号

子程序

返回整个里边表达的返回值。里边的变量都是局部的,修改不会带到外边。括号中多个命令之间用分号隔开,最后一个命令可以没有分号,各命令和括号之间不必有空格。

例如:

1
2
3
4
$ a=1
$ (a=3; echo $a); echo $a
3
1

命令替换

$(cmd)语句,与 `cmd` 等效。shell在执行一条命令时,会先将其中的 `cmd` 或 $(cmd) 中的语句当作命令执行一遍,再将结果加入到原命令中重新执行。注意有些shell不支持,如tcsh。

初始化数组

1
2
3
4
5
6
7
8
9
$ array=(a b c d)
$ for i in $array
> do
> echo $i
> done
a
b
c
d

(()):一对圆括号

数学计算

$(()) 可以进行整数型的运算。若是逻辑判断,表达式exp为真则为1,假则为0。比如:

1
2
$ echo "1+2=$(( 1 + 2 ))"
1+2=3

注意这种用法只适合是整数型的计算,不支持浮点型,输出的结果将自动转成十进制。更复杂的运算可以使用 expr 命令。

(()) 的用法与 let 一样:

1
2
3
4
5
6
$ a=5; ((a++))
$ echo $a
6
$ let a++
$ echo $a
7

for 循环

1
2
3
4
5
6
7
for (( condiction1; condiction2; condiction3 ))
do
.....
...
repeat all statements between do and
done until condiction2 is TRUE
Done

重定义变量值

[]:一个方括号

和 test 命令等效。

[[]]:一对方括号

[[ 是 bash 程序语言的关键字。并不是一个命令,[[ ]] 结构比[ ]结构更加通用。在 [[]] 之间所有的字符都不会发生文件名扩展或者单词分割,但是会发生参数扩展和命令替换。[[ ]]的主要优点有:

  1. 支持字符串的模式匹配,使用 =~ 操作符时甚至支持shell的正则表达式。字符串比较时可以把右边的作为一个模式,而不仅仅是一个字符串,比如 [[ hello == hell? ]],结果为真。[[ ]]中匹配字符串或通配符,不需要引号。
  2. 使用[[ ... ]]条件判断结构,而不是[ ... ],能够防止脚本中的许多逻辑错误。比如,&&||<>操作符能够正常存在于[[ ]]条件判断结构中,但是如果出现在[ ]结构中的话,会报错。
  3. bash把双中括号中的表达式看作一个单独的元素,并返回一个退出状态码。

{}:一对大括号

大括号扩展

示例:

1
$ cp $HOME/{.zshrc,.xprofile} $HOME/Dropbox/Documents/backup/

区分变量

例如:

1
$ var=abcd; echo ${var}EFG;

这样,Bash就不会认为变量是 varEFG 了。

匿名函数(代码块)

用来区分代码的,但是跟( )有个区别,就是在末尾要加上 ;。例如删除当前目录里面所有的 .svn 目录

1
$ find . -name .svn -type d -exec rm -fr {} \;

注意这里的 \ 就是将 ; 默认作为 shell meta 关闭,而使之成为 rm -fr {} 的分隔符。

子函数和代码块的异同:

  • ( )只是对一串命令重新开一个子shell进行执行,也称 nested sub-shell;
  • { }对一串命令在当前shell执行,也称为 non-named command group;
  • ( ){ }都是把一串的命令放在括号里面,并且命令之间用;号隔开;
  • ( )最后一个命令可以不用分号;
  • { }最后一个命令要用分号;
  • { }的第一个命令和左括号之间必须要有一个空格;
  • ( )里的各命令不必和括号有空格;
  • ( ){ }中括号里面的某个命令的重定向只影响该命令,但括号外的重定向则影响到括号里的所有命令。

通常而言,若所作的修改是临时的,且不想影响原有或以后的设定,那我们就用 ( ),反之,则用 { }

截取字符串

几种特殊的替换结构:

${var:-string}

若变量var为空,则使用${var:-string}中的string作为${var:-string}的值(有点像html图像的alt属性),否则变量var不为空时,则用${var:-string}中的var的值来替换${var:-string},如:

1
2
3
4
5
6
7
8
9
10
$ echo $newvar # newvar 为空
$ echo ${newvar:-a} # 使用备选的a
a
$ echo $newvar # newvar的值仍然是空
$ newvar=b
$ echo ${newvar:-a} # 变量newvar的值不为空
b
$

${var:=string}

${var:=string}的替换规则和${var:-string}是一样的,所不同之处是${var:=string}若var为空时,用string替换${var:=string}的同时,把string赋给变量var

1
2
3
4
5
6
7
8
9
10
$ echo $newvar
$ echo ${newvar:=a}
a
$ echo $newvar # 变量newvar被赋值为a,同时${newvar:=a}被替换成a
a
$ echo ${newvar:=b} # 变量newvar不为空(其值已被赋为a)
a
$ echo $newvar
a

${var:=string}很常用的一种用法是,判断某个变量是否赋值,没有的话则给它赋上一个默认值。

如设置默认的编辑器:

1
echo You use editor: ${EDITOR:=/bin/vi}

${var:+string}

${var:+string}的替换规则和上面的相反,即只有当var不是空的时候才替换成string,若var为空时则不替换或者说是替换成变量 var 的值,即空值。(因为变量var此时为空,所以这两种说法是等价的)

1
2
3
4
5
6
7
8
9
10
$ echo $newvar
a
$ echo ${newvar:+b}
b
$ echo $newvar
a
$ newvar=
$ echo ${newvar:+b}
$

${var:?string}

替换规则为:若变量var不为空,则用变量var的值来替换${var:?string};若变量var为空,则把string输出到标准错误中,并从脚本中退出。我们可利用此特性来检查是否设置了变量的值。

1
2
3
4
5
6
7
$ newvar=
$ echo ${newvar:?没有设置newvar的值}
bash: newvar: 没有设置newvar的值
n$ newvar=a
$ echo ${newvar:?没有设置newvar的值}
a
$

补充扩展

在上面这五种替换结构中string并不要求是常值,你也可以用另外一个变量的值或是一种命令的输出。

1
2
3
4
5
6
7
8
$ echo ${var:-`date`}
日 3月 6 02:10:39 CST 2005
$ echo ${var:-$(date)}
日 3月 6 02:11:46 CST 2005
$ a=test
$ echo ${var:-$a}
test
$

参考材料

  1. shell十三问
  2. Bash Shell 里的各种括号

Comments