Bash中的特殊字符

2015.07.06 | Comments

Bash中,用在脚本和其他地方的字符叫做特殊字符。下面依次举例介绍每个字符的用途。

#

行首以#(#!是个例外)开头是注释。

# This line is a comment.

注释也可以放在于本行命令的后边。

echo "A comment will follow."   # 注释在这里。

命令是不能放在同一行上注释的后边的。因为没有办法把注释结束掉,好让同一行上后边的”代码生效”,只能够另起一行来使用下一个命令。

在echo中转义的#是不能作为注释的,同样也可以出现在特定的参数替换结构中,或者是出现在数字常量表达式中。

echo "The # here does not begin a comment." 
echo 'The # here does not begin a comment.' 
echo The \# here does not begin a comment.    #转义字符\
echo The # 这里开始一个注释.
echo ${PATH#*:}   # 参数替换, 不是一个注释
echo $(( 2#101011 ))   # 数制转换, 不是一个注释

标准的引用和转义字符" ' \可以用来转义#,某些特定的模式匹配操作也可以使用#

;

分号作为命令行分隔符,可以在同一行上写两个或两个以上的命令。

echo hello; echo there

if [ -x "$filename" ]; then
    echo "File $filename exists.";
else
    echo "File $filename not found."; touch $filename 
fi; echo "File test complete."    

在某些情况下,; 也可以被转义。

;;

终止case选项。

case "$variable" in
    abc) echo "\$variable = abc" ;; 
    xyz) echo "\$variable = xyz" ;;
esac

.

点命令等价于source命令,这是一个bash的内建命令。

如果点放在文件名的开头的话,那么这个文件将会成为”隐藏”文件,并且ls命令将不会正常的显示出这个文件。

如果作为目录名的话,一个单独的点代表当前的工作目录,而两个点表示上一级目录。

点也可以表示当前目录。

cp /home/javachen/current_work/* .

当用作匹配字符的作用时,通常都是作为正则表达式的一部分来使用,点用来匹配任何的单个字符。

",'

双引号为部分引用,单引号为全引用。

echo "The # here does not begin a comment." 
echo 'The # here does not begin a comment.' 

,

逗号操作费,链接了一系列的算术操作。 虽然里边所有的内容都被运行了,但只有最后一项被返回。

let "t2 = ((a = 9, 15 / 3))" # a = 9   t2 = 15 / 3

也可以这样使用:

mkdir -p {a,b,c}  #创建三个目录a、b、c

\

转义符,一种对单字符的引用机制,通常是用于对单字符进行转义。\通常用来转义"',这样双引号和但引号就不会被解释成特殊含义了。

/

文件名路径分隔符,分隔文件名不同的部分,也可以用来作为除法算术操作符。

/home/bozo/projects/Makefile
let "t2 = ((a = 9, 15 / 3))" # a = 9   t2 = 15 / 3

`

命令替换,command结构可以将命令的输出赋值到一个变量中去。

date=`date`

:

空命令,等价于”NOP”,什么都不做,也可以被认为与shell的内建命令true作用相同。”:”命令是一个bash的内建命令,它的退出码是”true”,即为0。

:
echo $? # 0

死循环:

while :   # while true
do
    date
done

在 if/then 中的占位符,什么都不做,引出分支。

if condition
then :          # 什么都不做,引出分支
else
    take-some-action
fi

在一个二元命令中提供一个占位符。

n=1
: $((n = $n + 1))  # 如果没有":"的话,Bash 将会尝试把 $((n = $n + 1)) 解释为一个命令,运行时会报错
echo -n "$n "

在here document中提供一个命令所需的占位符或者用于注释代码。

: <<TESTVARIABLES
${HOSTNAME?}${USER?}${MAIL?} # 如果其中某个变量没被设置, 那么就打印错误信息. 
TESTVARIABLES


: <<COMMENTBLOCK
echo "This line will not echo."
This is a comment line missing the "#" prefix.
This is another comment line missing the "#" prefix.
&*@!!++=
The above line will cause no error message,
because the Bash interpreter will ignore it.
COMMENTBLOCK

使用参数替换来评估字符串变量。

: ${HOSTNAME ?} ${USER?} ${MAIL?}  #如果一个或多个必要的环境变量没被设置的话,就打印错误信息.

在与>重定向操作符结合使用时,将会把一个文件清空,但是并不会修改这个文件的权限。如果之前这个文件并不存在,那么就创建这个文件。

: > data.xxx  # 文件"data.xxx" 现在被清空了
# 与 cat /dev/null >data.xxx 的作用相同
# 然而,这并不会产生一个新的进程,因为":"是一个内建命令

在与>>重定向操作符结合使用时,将不会对预先存在的目标文件产生任何影响。如果这个文件之前并不存在,那么就创建它。这只适用于正规文件,,而不适用于管道、符号连接和某些特殊文件

: >> target_file

也可能用来作为注释行,虽然我们不推荐这么做。使用#来注释的话,将关闭剩余行的错误检查,所以可以在注释行中写任何东西。然而,使用:的话将不会这样。

: This is a comment that generates an error, ( if [ $x -eq 3] fi ).

”:”还用来在/etc/passwd和$PATH变量中做分隔符。

echo $PATH
/usr/java/default/bin:/usr/lib64/qt-3.3/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin

!

取反操作符。! 操作符将会反转命令的退出码的结果:

true        # "true" 是内建命令
echo "exit status of \"true\" = $?"         # 0

! true
echo "exit status of \"! true\" = $?"       # 1

如果一个命令以!开头,那么会启用Bash的历史机制。

true
!true
# 这次就没有错误了, 也没有反转结果.它只是重复了之前的命令(true).

在另一种上下文中,如命令行模式下,!还能反转bash的历史机制。需要注意 的是,在一个脚本中,历史机制是被禁用的。

$ history |head -n 10
   18  date
   19  ls
   20  cd
   21  pwd
   22  jps
   23  java
   24  ll
   25  ps
   26  history

#执行bash历史中的一条命令
$ !25

! 操作符还是Bash的关键字。

在一个不同的上下文中,! 也会出现在变量的间接引用中。

a=letter_of_alphabet
letter_of_alphabet=z

# 直接引用.
echo "a = $a"   # a = letter_of_alphabet

# 间接引用.
eval a=\$$a 
echo "Now a=$a"    #Now a = z

# 间接引用.
echo ${!a}  # a = z

*

用来做文件名匹配:

$ echo *
abs-book.sgml add-drive.sh agram.sh alias.sh

也可以用在正则表达式中,用来匹配任意个数(包含0个)的字符。

在算术操作符的上下文中, *号表示乘法运算。如果要做幂运算,使用**,这是求幂操作符。

测试操作符。在一个特定的表达式中,?用来测试一个条件的结果。

在一个双括号结构中,?就是C语言的三元操作符。

(( t = a<45?7:11 )) # C语言风格的三元操作

在参数替换表达式中,?用来测试一个变量是否set。

在通配中,用来做匹配单个字符的”通配符”,在正则表达式中,也是用来表示一个字符。

$

变量替换中,用于引用变量的内容。

var1=5
echo $var1

在一个变量前面加上$用来引用这个变量的值。

在正则表达式中,表示行结束符。

${} 是参数替换,$*, $@是位置参数,$? 是退出状态码变量,$$是进程id变量,保存所在脚本的进程 ID。

()

命令组:

(a=hello; echo $a)

在括号中的命令列表,将会作为一个子shell来运行。

例外: 在pipe中的一个大括号中的代码段可能运行在一个 子shell中。

ls | { read firstline; read secondline; }

#错误. 在大括号中的代码段, 将运行到子shell中, 所以"ls"的输出将不能传递到代码块中
echo "First line is $firstline; second line is $secondline"  # 不能工作

初始化数组:

Array=(element1 element2 element3)

{xxx,yyy,zzz,...}

大括号扩展:

cat {file1,file2,file3} > combined_file

cp file22.{txt,backup} # 拷贝"file22.txt"到"file22.backup"中

一个命令可能会对大括号中的以逗号分的文件列表起作用。在通配符中,将对大括号中的文件名做扩展。

在大括号中,不允许有空白,除非这个空白引用或转义。

$ echo {file1,file2}\ :{\ A," B",' C'}
file1 : A file1 : B file1 : C file2 : A file2 : B file2 : C

{}

代码块,又被称为内部组,这个结构事实上创建一个匿名函数。与”标准”函数不同的是,在其中的变量,对于脚本其他部分的代码来还是可见的。

{ local a; a=1; }
-bash: local: can only be used in a function
a=123
{ a=321; }
echo "a = $a"  # a = 321 (说明在代码块中对变量a所作的修改影响了外边的变量)

下边的代码展示在大括号结构中代码的I/O 重定向。

#!/bin/bash
# 从/etc/fstab中读行. 3
File=/etc/fstab

{
 read line1
 read line2
} < $File

echo "First line in $File is:" "$line1"
echo "Second line in $File is:" "$line2"
exit

与上面所讲到的()中的命令组不同的是,大括号中的代码块将不会开一个新的子shell。

'{}' \;

路径名。一般都在find命令中使用,这不是一个shell内建命令。;用来结束find命令序列的-exec选项,它需要被保护以防止被shell所解释。

find . -mtime -1 -type f -exec tar rvf archive.tar '{}' \;

[]

条件测试。

在一个array结构的上下文中,中括号用来引用数组中每个元素的编号。

Array[1]=a 
echo ${Array[1]}

用作正则表达式的一部分,方括号描一个匹配的字符范围。例如,正则表达式中,”[xyz]” 将会匹配字符x, y, 或z。

[[ ]]

测试表达式在[[ ]]中。

(( ))

双圆括号结构,扩展并计算在(( ))中的整数表达式。

与let命令很相似,((...))结构允许算术扩展和赋值。举个简单的例子,a=$(( 5 + 3 )),将把变量”a”设为”5 + 3”或者8。

> &> >& >> < <>

重定向。

重定向scriptname的输出到文件filename中。如果filename存在的,那么将会被覆盖:

scriptname >filename 

也可以清空文件内容:

> a.log 

重定向command的stdout和stderr到filename中:

command &>filename

重定向command的stdout到stderr中:

command >&2 

把scriptname的输出加到文件filename中。如果filename不存在的话,将被创建。

scriptname >> filename 

打开文件filename用来读写,并且分配文件描述符i给这个文件。如果filename不存在,这个文件将会创建:

[i]<>filename 

”<”或”>”还可以用于进程替换

在一种不同的上下文中,”<”和”>”可用来做字符串比较操作或者整数比较。

<<

用在here document中的重定向。

<<<

用在here string中的重定向。

\<,\>

正则表达式中的单词边:

grep '\<the\>' textfile

|

管道。分析前边命令的输出,并将输出作为后边命令的输入。这是一种产生命令链的好方法。

# 与一个简单的"ls -l"结果相同
echo ls -l | sh

# 合并和排序所有的".lst"文件, 然后删除所有重复的行.
cat *.lst | sort | uniq

输出的命令也可以传递到脚本中:

#!/bin/bash
# uppercase.sh : 修改输入, 全部转换为大写

tr 'a-z' 'A-Z'

exit 0

现在我们输送ls -l的输出到该脚本中:

$ ls -l | ./uppercase.sh
DRWXR-X--- 2 ROOT ROOT      4096 05-28 10:06 ML-1M
-RW-R--R-- 1 ROOT ROOT         0 2014-11-21 MONITOR_DISK.TXT
DRWXR-XR-X 2 ROOT ROOT      4096 2014-11-21 SCRIPT
-RW-R--R-- 1 ROOT ROOT        16 07-06 16:09 UPPERCASE.SH

管道中的每个进程的stdout比下一个进程作为stdin来读入,否则,数据流会阻塞,并且管道将产生一些非预期的行为:

# 从"cat file1 file2"中的输出并没出现
cat file1 file2 | ls -l | sort

作为子进程的运行的管道,不能够改变脚本的变量:

variable="initial_value"
echo "new_value" | read variable
echo "variable = $variable" # variable =initial_value

如果管道中的这个命令产生一个异常,并中途失败,那么这个管道将过早的终止,这种行为叫做broken pipe,并且这种状态下将发送一个SIGPIPE信号。

>|

强制重定向(使设置noclobber选项,就是-C选项),这将强制的覆盖一个现存文件。

||

或操作,在一个条件测试结构中,如果条件测试结构两边中的任意一边结果为true的话,||操作就会返回0(代表执行成功)。

&

后台运行命令,一个命令后边跟一个&表示在后台运行。

sleep 10 &

在一个脚本中,命令和循环都可能运行在后台:

for i in1 2 3 4 5 6 7 8 9 10
do
    echo -n "$i "
done & 

在一个脚本中,使用后台运行命令可能会使这个脚本挂起,直到敲ENTER 键,挂起的脚本才会恢复。看起来只有在这个命令的结果需要输出到stdout的时候,这种现象才会出现。

只要在后台运行命令的后边加上一个wait命令就会解决这个问题。

#!/bin/bash 
# test.sh

ls -l &
echo "Done."
wait

如果将后台运行命令的输出重定向到文件中或/dev/null中,也能解决这个问题。

&&

与逻辑操作。在一个条件测试结构中,只有在条件测试结构的两边结果都为true的时,&&操作才会返回0(代表sucess)。

-

选项,前缀。在所有的命令内如果使用选项参数的话,前边都要加上-

ls -al

sort -dfu $filename

set -- $variable

if [ $file1 -ot $file2 ]
then
    echo "File $file1 is older than $file2." 
fi

用于重定向stdin或stdout:

# 从一个目录移动整个目录树到另一个目录
(cd /source/directory && tar cf - . ) | (cd /dest/directory && tar xpvf -)

# 当然也可以这样写:
cp -a /source/directory/* /dest/directory
cp -a /source/directory/* /source/directory/.[^.]* /dest/directory  # 如果在/source/directory中有隐藏文件的话

bunzip2 -c linux-2.6.16.tar.bz2 | tar xvf -

注意,在这个上下文中,-本身并不是一个Bash操作,而是一个可以被特定的UNIX工具识别的选项,这些特定的UNIX工具特指那些可以写输出到stdout的工具,比如tar、cat等等。

$ echo "whatever" | cat -
whatever

使用diff命令来和另一个文件的一段进行比较:

grep Linux file1 | diff file2 -

一个更真实的例子是备份最后一天所有修改的文件:

#!/bin/bash

BACKUPFILE=backup-$(date +%m-%d-%Y)

# 如果在命令行中没有指定备份文件的文件名,那么将默认使用"backup-MM-DD-YYYY.tar.gz"
archive=${1:-$BACKUPFILE}

tar cvf - `find . -mtime -1 -type f -print` > $archive.tar

# 还有两种简单写法:
# find . -mtime -1 -type f -print0 | xargs -0 tar rvf  "$archive.tar"
# find . -mtime -1 -type f -exec tar rvf "$archive.tar" '{}' \;

gzip $archive.tar

echo "Directory $PWD backed up in archive file \"$archive.tar.gz\"."

exit 0

-还可以用来指先前的工作目录,cd -将会回到当前的工作目录,它使用了 $OLDPWD 环境变量。

另外,还可以当做减号来使用。

=

等号,赋值操作。

+

加号,也可以用在正则表达式中。某些内建命令使用+来打开特定的选项,用-来用这些特定的选项。

%

取模操作,也可以用于正则表达式。

~

home目录。

~+

当前工作目录,相当于$PWD内部变量。

~-

当前的工作目录,相当于$OLDPWD内部变量。

^

行首,在正则表达式中,^表示定位到文本行的行首。

控制字符

修改终端或文本显示的行为。控制字符以CONTROL + key这种方式进行组合(同时按下)。控制字符也可以使用8进制或16进制表示法来进行表示,但是前边必须要加上转义符。

控制字符比较多,这里不一一列出了。

空白

用来分隔函数,命令或变量。空白包含空格、tab、空行,或者是它们之间任意的组合体。在某些上下文中,比如变量赋值,空白是不被允许的,会产生语法错误。

空行不会影响脚本的行为,因此使用空行可以很好的划分独立的函数段以增加可读性。

特殊变量$IFS用来做一些输入命令的分隔符,默认情况下是空白。

如果想在字符串或变量中使用空白,那么应该使用引用。例如下面例子:

hello="A B  C   D"
echo $hello                  # A B C D
echo "$hello"               # A B  C   D

参考文章


原创文章,转载请注明: 转载自JavaChen Blog,作者:JavaChen
本文链接地址:http://blog.javachen.com/2015/07/06/bash-special-characters.html
本文基于署名2.5中国大陆许可协议发布,欢迎转载、演绎或用于商业目的,但是必须保留本文署名和文章链接。 如您有任何疑问或者授权方面的协商,请邮件联系我。