来源:http://blog.sina.com.cn/s/blog_62a151be0100x9rn.html

第四章 基本功 - 特殊符号

学习撰写 script 最迅速的捷径是观摩别人的 script 档案。不过,此法儘管实际,但这对于 shell 基础认知不足的人来说,通常都得到"事倍功半",甚至半途而废。通常第一个问题就是搞不懂那些"特殊符号"到底在代表什么意义,又到底她们干了些什么?

以下,农夫尝试列出常用的特殊符号表列,并在说明中加入简短的范例。原则上,"详细介绍"实非本章写作的原意。因为这些符号,在我后面打算撰写的章节,可能会再次出现。因此本章仅是表列性质。
 #     ;       ;;      .      ,        /       \          'string'
 |     !       $      ${}     $?       $$      $*         "string"
 *     **      ?       :      ^        $#      $@        `command`
{}     []     [[]]    ()     (())      ||      &&     {xx,yy,zz,...}
 ~     ~+      ~-     &     \<...\>     +      -             %
 =     ==      !=

输出/输入重导向
>     >>     <     <<     : >     &>     2&>     2<>
>&     >&2

不管如何,对任何人而言,想更进一步掌握这些符号的定义,最好的参考资料还是线上说明。而主动学习永远是最好的途径之一。至于深入理解与运用,是你自己的努力。

# 井号 (comments)

这几乎是个满场都有的符号,除了先前已经提过的"第一行"

#!/bin/bash

井号也常出现在一行的开头,或者位于完整指令之后,这类情况表示符号后面的是注解文字,不会被执行。

# This line is comments.
echo "a = $a" # a = 0

由于这个特性,当临时不想执行某行指令时,只需在该行开头加上 # 就行了。这常用在撰写过程中。

#echo "a = $a" # a = 0

如果她被用在指令中,或者引号双引号括住的话,或者在倒斜线的后面,那他就变成一般符号,不具上述的特殊功能。

~ 帐户的 home 目录

算是个常见的符号,代表使用者的 home 目录。

cd ~

也可以直接在符号后加上某帐户的名称

cd ~user

或者当成是路径的一部份

~/bin

~+ 当前的工作目录

这个符号代表当前的工作目录,她和内建指令 pwd 的作用是相同的。

# echo ~+
/var/log

~- 上次的工作目录

这个符号代表上次的工作目录。

# echo ~-
/etc/httpd/logs

; 分号 (Command separator)

在 shell 中,担任"连续指令"功能的符号就是"分号"。譬如以下的例子

cd ~/backup ; mkdir startup ; cp ~/.* startup/.

;; 连续分号 (Terminator)

专用在 case 的选项,担任 Terminator 的角色。

case "$fop" in
help) echo "Usage: Command -help -version filename" ;;
version) echo "version 0.1" ;;
esac

. 逗号 (dot)

在 shell 中,使用者应该都清楚,一个 dot 代表当前目录,两个 dot 代表上层目录。

CDPATH=.:~:/home:/home/web:/var:/usr/local

在上行 CDPATH 的设定中,等号后的 dot 代表的就是当前目录的意思。

如果档案名称以 dot 开头,该档案就属特殊档案,用 ls 指令必须加上 -a 选项才会显示。

除此之外,在 regular expression 中,一个 dot 代表匹配一个字元。

'string' 单引号 (single quote)

被单引号用括住的内容,将被视为单一字串。在引号内的代表变数的 $ 符号,没有作用,也就是说,他被视为一般符号处理。

heyyou=home
echo '$heyyou' # We get $heyyou

"string" 双引号 (double quote)

被双引号用括住的内容,将被视为单一字串。在引号内的代表变数的 $ 符号,仍旧可以代入变数内容。这点与单引数的处理方式不同。

heyyou=home
echo "$heyyou" # We get home

`command` 倒引号 (backticks)

在前面的单双引号,括住的是字串,但如果该字串是一列命令列,会怎样?答案是不会执行。要处理这种情况,我们得用倒单引号来做。

fdv=`date +%F`
echo "Today $fdv"

在倒引号内的 date +%F 会被视为指令,执行的结果会带入 fdv 变数中。

, 逗点 (comma)

这个符号常运用在运算当中当做"区隔"用途。如下例

#!/bin/bash

let "t1 = ((a = 5 + 3, b = 7 - 1, c = 15 / 3))"
echo "t1 = $t1, a = $a, b = $b"

/ 斜线 (forward slash)

在路径表示时,她代表目录。

cd /etc/rc.d

cd ../..

cd /

通常单一的 / 代表 root 根目录的意思。这相当常用,也应该很容易理解。此外,她也在四则运算中,代表除法的符号。

let "num1 = ((a = 10 / 2, b = 25 / 5))"

\ 倒斜线 (escape)

在交谈模式下的 escape 字元,有几个作用;放在指令前,有取消 aliases 的作用;放在特殊符号前,则该特殊符号的作用消失;放在指令的最末端,表示指令连接下一行。

# type rm
rm is aliased to `rm -i'

# \rm ./*.log

上例,我在 rm 指令前加上 escape 字元,作用是暂时取消别名的功能,将 rm 指令还原。

# bkdir=/home
# echo "Backup dir, \$bkdir = $bkdir"
Backup dir, $bkdir = /home

上例 echo 内的 \$bkdir,escape 将 $ 变数的功能取消了,因此,会输出 $bkdir,而第二个 $bkdir 则会输出变数的内容 /home。

| 管线 (pipeline)

pipeline 是 UNIX 系统,基础且重要的观念。连结上个指令的标准输出,做为下个指令的标准输入。

who | wc -l

善用这个观念,对精简 script 有相当的帮助。

! 惊嘆号(negate or reverse)

通常它代表反逻辑的作用,譬如条件侦测中,用 != 来代表"不等于"

if [ "$?" != 0 ]
then
echo "Executes error"
exit 1
fi

还有,在 Regular Expressions 中她担任 "反逻辑" 的角色

ls a[!0-9]

上例,代表排除显示 a0, a1 .... a9 这几个档案。

: 冒号

在 bash 中,人称露二点的冒号,是个道地的内建指令。她负责执行一件很伟大的事,就是"什么事都不干",但回应状态值 0。

:
echo $? # 回应为 0

来看个应用她的例子

: > f.$$

上面这一行,相当于 cat /dev/null > f.$$。不仅写法简短了,而且执行效率也好上许多。

有时,也会出现以下这类的用法

: ${HOSTNAME?} ${USER?} ${MAIL?}

这行的作用是,检查这些环境变数是否已宣告,没有宣告的将会以标准错误显示错误讯息。像这种检查如果使用类似 test 或 if 这类的做法,基本上也可以处理。但都比不上,上例的简洁与效率。

除了上述之外,还有一个地方必须使用冒号

PATH=$PATH:$HOME/fbin:$HOME/fperl:/usr/local/mozilla

在使用者自己的 HOME 目录下的 .bash_profile 或任何功能相似的档案中,设定关于 "路径" 变数的场合中,我们都使用冒号,来做区隔。这是她在 bash 中,另一个常被使用的场合。

? 问号 (wild card)

她在档名扩展(Filename expansion)上扮演的角色是匹配一个任意的字元,但不包含 null 字元。

# ls a?
a1

善用她的特点,可以做比较精确的档名匹配。

* 星号 (wild card)

相当常用的符号。在档名扩展(Filename expansion)上,她用来代表任何字元,包含 null 字元。我个人喜欢叫他 - 星哥。

# ls a*
a a1 access_log

在运算时,它则代表 "乘法"。

let "fmult=2*3"

抱歉,例子简单的有点过分了,请原谅我的电脑,她算数不好。除了内建指令 let,还有一个关于运算的指令 expr,星号在这里也担任"乘法"的角色。不过在使用上得小心,他的前面必须加上 escape 字元。

** 次方运算

两个星号在运算时代表 "次方" 的意思。

let "sus=2**3"
echo "sus = $sus" # sus = 8

这玩意,农夫打从知道以来还不曾用过,换句话说叫 "白知"。

$ 钱号(dollar sign)

基本上,她对我来说永远代表"美元"的意思。如果你上街问人....相信没有会告诉你以下的两种答案(如果有,我头给你)。

她是变数替换(Variable Substitution)的代表符号。关于这个,在此之前,其实以经用过许多回了。

vrs=123
echo "vrs = $vrs" # vrs = 123

另外,她在 Regular Expressions 里被定义为 "行" 的最末端 (end-of-line)。这个常用在 grep、sed、awk 以及 vim(vi) 当中。

${} 变数的 Parameter Expansion

bash 对 ${} 定义了不少用法。以下是取自线上说明的表列

${parameter:-word}
    ${parameter:=word}
    ${parameter:?word}
    ${parameter:+word}
    ${parameter:offset}
    ${parameter:offset:length}
    ${!prefix*}
    ${#parameter}
    ${parameter#word}
    ${parameter##word}
    ${parameter%word}
    ${parameter%%word}
    ${parameter/pattern/string}
    ${parameter//pattern/string}

这....,是个不小的课题。日后择文说明。

$* 

引用 script 的执行引数,是常有的。引数的算法与一般指令相同,指令本身为引数 0,其后为引数 1,然后依此类推。引用变数的代表方式如下

$0, $1, $2, $3, $4, $5, $6, $7, $8, $9, ${10}, ${11}.....

个位数的引数,可直接使用数字,但两位数以上,则必须使用 {} 符号来括住。

$* 则是代表所有引数的符号。使用时,得视情况加上双引号。

echo "$*"

还有一个与 $* 具有相同作用的符号,但效用与处理方式略为不同的符号。

$@

$@ 与 $* 具有相同作用的符号,不过她们两者有一个不同点。

符号 $* 将所有的引数视为一个整体。但符号 $@ 则仍旧保留每个引数的区段观念。让我们用 for loop 分别处理这两个符号,就能清楚一些。

# cat arg-02
#!/bin/bash

index=1

# About $*
echo -e "\nListing args with \"\$*\":"
for arg in "$*"
do
echo "Arg #$index = $arg"
let "index+=1"
done
echo -e "Entire arg list seen as single word.\n\n"

index=1

# About $@
echo "Listing args with \"\$@\":"
for arg in "$@"
do
echo "Arg #$index = $arg"
let "index+=1"
done
echo "Arg list seen as separate words."
#

下面是执行结果

# ./arg-02 a b c d e f g h i j k

Listing args with "$*":
Arg # = a b c d e f g h i j k
Entire arg list seen as single word.

Listing args with "$@":
Arg #1 = a
Arg #2 = b
Arg #3 = c
Arg #4 = d
Arg #5 = e
Arg #6 = f
Arg #7 = g
Arg #8 = h
Arg #9 = i
Arg #10 = j
Arg #11 = k
Arg list seen as separate words.
#

有兴趣不妨自己处理看看她们的区别。

$#

这也是与引数相关的符号,她的作用是告诉你,引数的总数量是多少。

echo "$#"

$? 状态值 (status variable)

一般来说,UNIX(linux) 系统的 process 以执行 system call exit() 来作为终结的。这个回传值就是 status 值。她回传给 parent process,作为检查 child process 执行状态使用。

一般指令程序倘若执行成功,其回传值为 0;失败为 1。这在撰写 script 时,使用的相当频繁。

tar cvfz dfbackup.tar.gz /home/user > /dev/null
echo "$?"

$$

前面曾经简单地提到程序的执行,这个符号与程序有关。由于程序的 ID 是唯一的,所以在同一个时间,不可能有重复性的 PID。先理解此点。

有时,撰写 script 会需要产生暂态档案,用来存放必要的资料。而此 script 亦有可能在同一时间被使用者们同时使用。在这种情况下,固定档名的暂态档,在写法上就显的不可靠,也不可行。唯有产生动态档名,才能符合需要。但如何做??

符号 $$ 或许可以符合这种需求。它代表当前 shell 的 PID。

echo "$HOSTNAME, $USER, $MAIL" > ftmp.$$

使用它来作为档名的一部份,可以避免在同一时间,产生同档名的覆盖现象。

ps: 基本上,系统会回收执行完毕的 PID,然后再次依需要分配使用。所以 script 即使暂存档是使用动态档名的写法,如果 script 执行完毕后仍不加以清除,会衍生其他问题。此点也必须注意。

() 指令群组 (command group)

用括号将一串连续指令括起来,这种用法对 shell 来说,称为指令群组。如下面的例子

(cd ~ ; vcgh=`pwd` ; echo $vcgh)

指令群组有一个特性,shell会以产生 subshell 来执行这组指令。因此,在其中所定义的变数,仅作用于指令群组本身。我们来看个例子

# cat ftmp-01
#!/bin/bash
a=fsh
(a=incg ; echo -e "\n $a \n")
echo $a

# ./ftmp-01

incg

fsh

除了上述的指令群组,括号也用在 array 变数的定义上;另外也应用在其他可能需要加上 escape 字元才能运作的场合,如运算式。这些我就不在举例了,后续的章节中会再次提到。

(())

这组符号的作用与 let 指令相似,用在算数运算上。只不过她是 bash 的内建功能。所以,在执行效率上会比使用 let 指令要好许多。

#!/bin/bash

(( a = 10 ))
echo -e "inital value, a = $a\n"

(( a++ ))
echo "after a++, a = $a"

{} 大括号 (Block of code)

有时候 script 当中会出现,大括号中会夹着一段或几段以"分号"做结尾的指令或变数设定。

# cat ftmp-02
#!/bin/bash
a=fsh
{a=inbc ; echo -e "\n $a \n"}
echo $a

# ./ftmp-02

inbc

inbc

这种用法与上面介绍的指令群组非常相似,但有个不同点,她在当前的 shell 执行,不会产生 subshell。

大括号也被运用在 "函数" 的功能上。广义地说,单纯只使用大括号时,作用就像是个没有指定名称的函数一般。因此,善用她来撰写 script 也是相当好的一件事。尤其对输出输入的重导向上,这个做法可精简 script 的复杂度。

此外,大括号还有另一种用法,如下

{xx,yy,zz,...}

这种大括号的组合,常用在字串的组合上,来看个例子
mkdir {userA,userB,userC}-{home,bin,data}

我们得到 userA-home, userA-bin, userA-data, userB-home, userB-bin, userB-data, userC-home, userC-bin, userC-data,这几个目录。这组符号在适用性上相当广泛。能加以善用的话,回报是精简与效率。像下面的例子
chown root /usr/{ucb/{ex,edit},lib/{ex?.?*,how_ex}}

如果不是因为支援这种用法,我们得写几行重复几次呀!

[] 中括号

她常出现在流程控制中,扮演括住判断式的作用。抱歉!回锅个例子
if [ "$?" != 0 ]
then
echo "Executes error"
exit 1
fi

这个符号在 Regular Expressions 中担任类似 "范围" 或 "集合" 的角色
rm -r 200[1234]

上例,代表删除 2001, 2002, 2003, 2004 等目录的意思。

除此之外,她也有个"双层汉堡"的用法

[[]]

这组符号与先前的 [] 符号,基本上作用相同,但她允许在其中直接使用 || 与 && 逻辑等符号。
#!/bin/bash
read ak
if [[ $ak > 5 || $ak < 9 ]]
then
echo $ak
fi

|| 逻辑符号

这个会时常看到,代表 or 逻辑的符号。

&& 逻辑符号

这个也会常看到,代表 and 逻辑的符号。

& 背景工作

单一个 & 符号,且放在完整指令列的最后端,即表示将该指令列放入背景中工作。

tar cvfz data.tar.gz data > /dev/null &

\<...\> 单字边界

这组符号在 Regular Expressions 中,被定义为"边界"的意思。譬如,当我们想找寻 the 这个单字时,如果我们用

grep the FileA

你将会发现,像 there 这类的单字,也会被当成是匹配的单字。因为 the 正巧是 there 的一部份。如果我们要必免这种情况,就得加上 "边界" 的符号

grep '\' FileA

这就是她的作用。

+ 加号 (plus)

在运算式中,她用来表示 "加法"。

expr 1 + 2 + 3

这个应该没有问题。此外她也在 Regular Expressions 中,用来表示"很多个"的前面字元的意思。

# grep '10\+9' fileB
109
1009
100009
1000093
1010009
#

这个符号在使用时,前面必须加上 escape 字元。

- 减号 (dash)

在运算式中,她用来表示 "减法"。

expr 10 - 2

此外也是系统指令的选项符号。

ls -expr 10 - 2

在 GNU 指令中,如果单独使用 - 符号,不加任何该加的档案名称时,她便代表"标准输入"的意思。这是 GNU 指令的共通选项。譬如下例

tar xpvf -

这里的 - 符号,既代表从标准输入读取资料。

不过,她在 cd 指令中则比较特别

cd -

这代表变更工作目录到"上一次"工作目录。

% 除法 (Modulo)

在运算式中,她用来表示 "除法"。

expr 10 % 2

此外,她也被运用在关于变数的 Parameter Expansion 当中的下列

${parameter%word}
${parameter%%word}

一个 % 表示最短的 word 匹配,两个表示最长的 word 匹配。这个项目以后会再详细介绍。

= 等号 (Equals)

常在设定变数时看到的符号。

vara=123
echo " vara = $vara"

或者像是 PATH 的设定,甚至应用在运算或判断式等此类用途上。

== 等号 (Equals)

常在条件判断式中看到,代表 "等于" 的意思。

if [ $vara == $varb ]
...下略

!= 不等于

常在条件判断式中看到,代表 "不等于" 的意思。

if [ $vara != $varb ]
...下略

^

这个符号在 regular expression 中,代表行的 "开头" 位置。

输出/输入重导向 

关于 "输出/输入重导向" 的符号,不仅是常用,同时也是非常重要的基础。

>, >>, <, <<

这四个兄弟,大概不须多讲
: >, &>, 2&>, >&, >&2, 2<>

这挂兄弟其实也蛮常用的,不过此部分农夫会以独立的章节来介绍她们。在此就不再赘述了。

本篇的整理暂时到此,日后视情况再做修订。

网路农夫 2004/05/17

写作资料参考

本章之所以能顺利写作,得助参考于 Mendel Cooper 的力着 Advanced Bash-Scripting Guide (Part 2.3)。原则上,本文在写作架构与资料整理上,参考自该文,但非迻译自该文。一则是我并没有取得 Mendel Cooper 的同意,迻译该文。二方面,是农夫本人没有翻译的英文语言能力。

因此,特以此声明,表达对 Mendel Cooper 无私奉献的写作精神的感谢与敬意。

shell 中的特殊符号的含义的更多相关文章

  1. Linux Shell中的特殊符号和含义简明总结(包含了绝大部份)

    case语句适用于需要进行多重分支的应用情况. case分支语句的格式如下: case $变量名 in 模式1) 命令序列1 ;; 模式2) 命令序列2        ;; *) 默认执行的命令序列  ...

  2. shell中的特殊符号总结

    在shell中常用的特殊符号罗列如下: # ;   ;; . , / \\ 'string'| !   $   ${}   $? $$   $* \"string\"* **   ...

  3. linux shell中的特殊符号

    该内容,均来自此网址(http://www.92csz.com/study/linux/12.htm).在下只是把那些命令的截图给去了. 你在学习linux的过程中,也许你已经接触过某个特殊符号,例如 ...

  4. [转帖]shell中的特殊符号总结

    http://www.embeddedlinux.org.cn/emb-linux/entry-level/201907/18-8747.html 在shell中常用的特殊符号罗列如下: # ;   ...

  5. shell 中>/dev/null 2>&1含义

    shell中可能经常能看到:>/dev/null 2>&1 命令的结果可以通过%>的形式来定义输出 分解这个组合:“>/dev/null 2>&1” 为五 ...

  6. linux中的特殊符号及其含义梳理

    1. 重定向符号及含义 注意:箭头流向即是数据的流向. 数字0:标准输入(standard input,简写stdin),数据从右往左方向流动 数字1:标准正确输出(standard output,简 ...

  7. shell中的特殊符号

    Shell符号及各种解释对照表: Shell符号 使用方法及说明 # 注释符号(Hashmark[Comments]) 1.在shell文件的行首,作为shebang标记,#!/bin/bash; 2 ...

  8. shell中各种美元符号组合

    $ 这个程式的执行名字 $n 这个程式的第n个参数值,n=.. $* 这个程式的所有参数,此选项参数可超过9个. $# 这个程式的参数个数 $$ 这个程式的PID(脚本运行的当前进程ID号) $! 执 ...

  9. shell中cmd1 && cmd2 || cmd3的含义

    在某些情况下,很多指令我想要一次输入去执行,而不想要分次去执行时,就要用到 && || 了.cmd 1 && cmd21,若cmd1执行完毕之后且正确执行($?=0), ...

随机推荐

  1. 转:详解JMeter正则表达式(1)

    1.概览 JMeter中包含范本匹配软件Apache Jakarta ORO .在Jakarta网站上有一些关于它的文档,例如a summary of the pattern matching cha ...

  2. C#键盘事件处理

    键盘事件是在用户按下键盘上的一个键的时候发生的,可分为两类.第一类是KeyPress事件,当按下的键表示的是一个ASCII字符的时候就会触发这类事件,可通过他的KeyPressEventArgs类型参 ...

  3. String.getBytes()--->字符串转字节数组

    是String的一个方法String的getBytes()方法是得到一个系统默认的编码格式的字节数组getBytes("utf-8") 得到一个UTF-8格式的字节数组把Strin ...

  4. Spring Security(13)——session管理

    1.1     检测session超时 1.2     concurrency-control 1.3     session 固定攻击保护 Spring Security通过http元素下的子元素s ...

  5. Spring Security(05)——异常信息本地化

    Spring Security支持将展现给终端用户看的异常信息本地化,这些信息包括认证失败.访问被拒绝等.而对于展现给开发者看的异常信息和日志信息(如配置错误)则是不能够进行本地化的,它们是以英文硬编 ...

  6. asp.net正则表达式去除a标签

    if (drr["allow_a"].ToString() == "False") { cont = dr["news_Content"]. ...

  7. webapi中的自定义路由约束

    Custom Route Constraints You can create custom route constraints by implementing the IHttpRouteConst ...

  8. webapi中的扩展点

    Extension Points Web API provides extension points for some parts of the routing process. Interface ...

  9. Java comparable 和 comparator

    一.comparator 接口继承 public class ComparatorTest { /** * @param args */ public static void main(String[ ...

  10. 禁用 VS2010 的 vcpkgsrv.exe 运行

    VS2010经常使用一段时间后巨卡,发现vcpkgsrv.exe这个进程相当占内存,但是结束后又会自己启动,百度之原来是IntelliSense的问题,关闭之即可,设置如下