Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/wzb56/13_questions_of_shell

shell十三问--shell教程(markdown 版本)
https://github.com/wzb56/13_questions_of_shell

Last synced: 5 days ago
JSON representation

shell十三问--shell教程(markdown 版本)

Awesome Lists containing this project

README

        

# 13_questions_of_shell
shell十三问--shell教程(markdown 版本)

##shell十三问之1: 何为shell?
_______________________________________

`shell`是什么东西之前,不妨让我们重新审视`使用者`和`计算机系统`的关系:
(此处为使用者和计算机系统的关系图)

我们知道计算机的运作不能离开硬件,但使用者却无法直接操作硬件,
硬件的驱动只能通过一种称为“`操作系统`(`OS`,`Opertating System`)”的软件来管控。
事实上,我们每天所谈的“`linux`”,严格来说只是一个`操作系统`(`OS`),
我们称之为“`内核`(`kernel`)”。

然而,从使用者的角度来说,使用者没有办法直接操作一个`kernel`,
而是通过`kernel`的“外壳”程序,也就是所谓的`shell`,来与`kernel`沟通。
这也正是`kernel`跟`shell`的形象命名的的关系。如图:
(此处为kernel-->shell关系图;)

从技术的角度来说,`shell`是一个使用者与系统的`交互界面(interface)`,
只能让使用者通过`命令行`(`command line`)来使用系统来完成工作。
因此,`shell`最简单的定义就是----`命令解释器`( `Command Interpreter`):

- 将使用者的命令翻译给kernel来处理;
- 同时,将kernel的处理结果翻译给使用者。

每次当我们完成`系统登入`(`login`), 我们就取得一个交互模式的shell,
也称之为`login shell` 或者 `primary shell`。

若从`进程`(`process`)的角度来说,我们在shell所下达的命令,均是shell所产生的`子进程`。
这种现象,我暂可称之为`fork`。

如果是执行`shell脚本`(`shell script`)的话,脚本中命令则是由另一个非交互模式的
`子shell`(`sub shell`)来执行的。 也就是primary shell产生sub shell的进程,而该sub shell
进程再产生script中所有命令的进程。
(关于进程,我们日后有机会在补充)

这里, 我们必须知道:`kernel` 与 `shell` 是不同的两套软件,而且都是可以被替换的:

- 不同的`OS`使用不同的`kernel`;
- 同一个kernel之上,也可以使用不同的`shell`;

在`Linux`的预设系统中,通常可以找到好几种不同的`shell`,
且通常会被记录在如下文件中:
```shell
/etc/shells
```

不同的`shell`有着不同的功能,且彼此各异,或者说“大同小异”。
常见的`shell`主要分为两大主流:

1. sh:
- burne shell (sh)
- burne again shell (bash)
2. csh:
- c shell (csh)
- tc shell (tcsh)
- korn shell (ksh)
(FIXME)

大部分的Linux操作系统的预设shell都是`bash`,其原因大致如下两种:
- 自由软件
- 功能强大
bash是gnu project最成功的产品之一,自推出以来深受广大`Unix`用户的喜爱,
且也逐渐成为不少组织的系统标准。

##shell十三问之2:shell prompt(PS1)与Carriage Return(CR)关系
_____________________________________________________________

当你成功登陆一个shell终端的文字界面之后,大部分的情形下,
你会在屏幕上看到一个不断闪烁的方块或者底线(视不同的版本而别),
我们称之为`游标`(`cursor`).
`cursor`作用就是告诉你接下来你从键盘输入的按键所插入的位置,
且每输入一个键,`cursor`便向右移动一个格子,
如果连续输入太多的话,则自动接在下一行输入。

假如你刚完成登陆,还没有输入任何按键之前,
你所看到的`cursor`所在的位置的同一行的左边部分,我们称之为`提示符`(`prompt`)。

`提示符`的格式或因不同的版本而各有不同,
在Linux上,只需留意最接近`游标`的一个提示符号,通常是如下两者之一:

- $: 给一般用户账号使用;
- \#: 给root(管理员)账号使用;

事实上,shell prompt的意思很简单:
告诉shell使用者,您现在可以输入命令行了。

我们可以说,使用者只有在得到shell prompt才能打命令行,
而`cursor`是指示键盘在命令行的输入位置,使用者每输入一个键,
`cursor`就往后移动一个格,直到碰到命令行读进`CR`(`Carriage Return`, 由`Enter`键产生)字符为止。
`CR`的意思也很简单:
使用者告诉shell:老兄,你可以执行的我命令行了。
严格来说:
所谓的命令行, 就是在`shell prompt`与`CR`之间所输入的文字。

(**question:为何我们这里坚持使用`CR`字符而不说`Enter`按键呢?
答案在后面的学习中给出**)。

不同的命令可以接受的命令的格式各有不同,
一般情况下,一个标准的命令行格式为如下所列:
```shell
command-name options argument
```

若从技术的细节上来看,
shell会依据`IFS`(`Internal Field Seperator`) 将 command line
所输入的文字给拆解为`字段`(`word`).
然后在针对特殊的字符(meta)先做处理,最后在重组整行command line。

(**注意:请务必理解以上两句的意思,我们日后的学习中常回到这里思考**。)

其中`IFS`是shell预设使用的字段位分隔符号,可以由一个及多个如下按键组成:

- 空白键(White Space)
- 表格键(Tab)
- 回车键(Enter)

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

- 确的路径所指定的外部命令
- 命令的别名(alias)
- shell内建命令(built-in)
- $PATH之下的外部命令

每一个命令行均必须包含命令的名称,这是不能缺少的。

##shell十三问之3:别人echo、你也echo,是问echo知多少?
-----------------------------------------------------

承接上一章介绍的`command line`, 这里我们用`echo`这个命令加以进一步说明。

> **温习**
> 标准的`command line`三个组成部分:`command_name option argument`

`echo`是一个非常简单、直接的Linux命令:

```shell
$echo argument
```
`echo`将argument送出到`标准输出`(`stdout`),通常是在监视器(monitor)上输出。

> **Note:**

> 在linux系统中任何一个进程默认打开三个文件:stdin、stdout、stderr.

> stdin 标准输入

> stdout 标准输出

> stderr 标准错误输出

为了更好理解,不如先让我们先跑一下`echo`命令好了:
```shell
$echo

$
```
你会发现只有一个空白行,然后又回到了`shell prompt`上了。
这是因为`echo`在预设上,在显示完argument之后,还会送出以一个换行符号
(`new-line charactor`).
但是上面的command `echo`并没有任何argument,那结果就只剩一个换行符号。
若你要取消这个换行符号, 可以利用`echo`的`-n` 选项:
```shell
$echo -n
$
```
不妨让我们回到`command line`的概念上来讨论上例的echo命令好了:
`command line`只有command_name(`echo`)及option(`-n`),并没有显示任何`argument`。

要想看看`echo`的`argument`,那还不简单接下来,你可以试试如下的输入:
```shell
$echo first line
first line
$echo -n first line
first line $
```
以上两个`echo`命令中,你会发现`argument`的部分显示在你的屏幕,
而换行符则视 `-n` 选项的有无而别。
很明显的,第二个`echo`由于换行符被取消了,
接下来的`shell prompt`就接在输出结果的同一行了... ^_^。

事实上,`echo`除了`-n` 选项之外,常用选项有:

- -e: 启用反斜杠控制字符的转换(参考下表)
- -E: 关闭反斜杠控制字符的转换(预设如此)
- -n: 取消行末的换行符号(与-e选项下的\c字符同意)

关于`echo`命令所支持的反斜杠控制字符如下表:

|转义字符|字符的意义|
---------|----------|
|\a | ALERT / BELL(从系统的喇叭送出铃声)|
|\b | BACKSPACE, 也就是向左退格键|
|\c | 取消行末之换行符号
|\E | ESCAPE, 脱字符键|
|\f | FORMFEED, 换页字符|
|\n | NEWLINE, 换行字符 |
|\r | RETURN, 回车键|
|\t | TAB, 表格跳位键|
|\v | VERTICAL TAB, 垂直表格跳位键|
|\n | ASCII 八进制编码(以x开头的为十六进制),此处的n为数字
|\\ | 反斜杠本身|

> **Note:**
> 上述表格的资料来自O'Reilly出版社的**Learning the Bash Shell, 2nd Ed**.

或许,我们可以通过实例来了解`echo`的选项及控制字符:

例一:
```shell
$ echo -e "a\tb\tc\n\d\te\tf"
a b c
d e f
$
```
上例中,用\t来分割abc还有def,及用\n将def换至下一行。

例二:
```shell
$echo -e "\141\011\142\011\143\012\144\011\145\011\146"
a b c
d e f
```
与例一中结果一样,只是使用ASCII八进制编码。

例三:
```shell
$echo -e "\x61\x09\x62\x09\x63\x0a\x64\x09\x65\x09\x66"
a b c
d e f
```
与例二差不多,只是这次换用ASCII的十六进制编码。

例四:
```shell
$echo -ne "a\tb\tc\nd\te\bf\a"
a b c
d f $
```
因为e字母后面是退格键(\b),因此输出结果就没有e了。
在结束的时听到一声铃响,是\a的杰作。
由于同时使用了-n选项,因此`shell prompt`紧接在第二行之后。
若你不用-n的话,那你在\a后再加个\c,也是同样的效果。

事实上,在日后的`shell`操作及`shell script`设计上,
`echo`命令是最常被使用的命令之一。
比方说,使用`echo`来检查变量值:
```shell
$ A=B
$ echo $A
B
$ echo $?
0
```

> **Note:**
> 关于变量的概念,我们留到以下的两章跟大家说明。

好了,更多的关于`command line`的格式, 以及`echo`命令的选项,
请您自行多加练习、运用了...

##shell十三问之4:""(双引号)与''(单引号)差在哪?
------------------------------------------------

还是回到我们的`command line`来吧...

经过前面两章的学习,应该很清楚当你在`shell prompt`后面敲打键盘,
直到按下`Enter`键的时候,你输入的文字就是`command line`了,
然后`shell`才会以进程的方式执行你所交给它的命令。
但是,你又可知道:你在`command line`中输入的每一个文字,
对`shell`来说,是有类别之分的呢?

简单而言,(我不敢说精确的定义,注1),
`command line`的每一个`charactor`, 分为如下两种:

- literal:也就是普通的纯文字,对`shell`来说没特殊功能;
- meta: 对`shell`来说,具有特定功能的特殊保留元字符。

> **Note:**

> 对于`bash shell`在处理`comamnd line`的顺序说明,
> 请参考O'Reilly出版社的**Learning the Bash Shell,2nd Edition**,
> 第177-180页的说明,尤其是178页的流程图:Figure 7-1 ...

`literal`没什么好谈的,
像abcd、123456这些"文字"都是literal...(so easy? ^_^)
但meta却常使我们困惑...(confused?)
事实上,前两章,我们在`command line`中已碰到两个
似乎每次都会碰到的meta:

- `IFS`:有`space`或者`tab`或者`Enter`三者之一组成(我们常用space)
- `CR`: 由`Enter`产生;

`IFS`是用来拆解`command line`中每一个词(word)用的,
因为`shell command line`是按词来处理的。
而`CR`则是用来结束`command line`用的,这也是为何我们敲`Enter`键,
命令就会跑的原因。

除了常用的`IFS`与`CR`, 常用的meta还有:

|meta字符| meta字符作用|
|--------|-------------|
|= |设定变量|
|$ | 作变量或运算替换(请不要与`shell prompt`混淆)|命令
|>| 输出重定向(重定向stdout)|
|<|输入重定向(重定向stdin)|
|\||命令管道|
|&|重定向file descriptor或将命令至于后台(bg)运行|
|()|将其内部的命令置于nested subshell执行,或用于运算或变量替换|
|{}|将期内的命令置于non-named function中执行,或用在变量替换的界定范围|
|;|在前一个命令执行结束时,而忽略其返回值,继续执行下一个命令|
|&&|在前一个命令执行结束时,若返回值为true,继续执行下一个命令|
|\|\||在前一个命令执行结束时,若返回值为false,继续执行下一个命令|
|!|执行histroy列表中的命令|
|... | ...|


假如我们需要在`command line`中将这些保留元字符的功能关闭的话,
就需要quoting处理了。

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

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

> **Note:**

> 在soft quote中被豁免的具体meta清单,我不完全知道,
> 有待大家补充,或通过实践来发现并理解。

下面的例子将有助于我们对quoting的了解:
```shell
$ A=B C #空白符未被关闭,作为IFS处理
$ C:command not found.
$ echo $A

$ A="B C" #空白符已被关掉,仅作为空白符
$ echo $A
B C
```
在第一个给A变量赋值时,由于空白符没有被关闭,
`command line` 将被解释为:
`A=B 然后碰到,接着执行C命令`
在第二次给A变量赋值时,由于空白符被置于soft quote中,
因此被关闭,不在作为`IFS`;
`A=BC`
事实上,空白符无论在soft quote还是在hard quote中,
均被关闭。Enter键字符亦然:

```shell
$ A=`B
> C
> '
$ echo "$A"
B
C
```
在上例中,由于`enter`被置于hard quote当中,因此不再作为`CR`字符来处理。
这里的`enter`单纯只是一个断行符号(new-line)而已,
由于`command line`并没得到`CR`字符,
因此进入第二个`shell prompt`(`PS2`, 以>符号表示),
`command line`并不会结束,直到第三行,
我们输入的`enter`并不在hard quote里面,
因此没有被关闭,
此时,`command line`碰到`CR`字符,于是结束,交给shell来处理。

上例的`Enter`要是被置于soft quote中的话,`CR`字符也会同样被关闭:

```shell
$ A="B
> C
> "
$ echo $A
B C
```
然而,由于 `echo $A`时的变量没有置于soft quote中,
因此,当变量替换完成后,并作命令行重组时,`enter`被解释为`IFS`,
而不是new-line字符。

同样的,用escape亦可关闭CR字符:
```shell
$ A=B\
> C\
>
$ echo $A
BC
```
上例中的,第一个`enter`跟第二个`enter`均被escape字符关闭了,
因此也不作为`CR`来处理,但第三个`enter`由于没有被escape,
因此,作为`CR`结束`command line`。
但由于`enter`键本身在shell meta中特殊性,在 \ escape字符后面
仅仅取消其`CR`功能, 而不保留其IFS功能。

你或许发现光是一个`enter`键所产生的字符,就有可能是如下这些可能:

- CR
- IFS
- NL(New Line)
- FF(Form Feed)
- NULL
- ...

至于,什么时候解释为什么字符,这个我就没法去挖掘了,
或者留给读者君自行慢慢摸索了...^-^

至于soft quote跟hard quote的不同,主要是对于某些meta的关闭与否,以$来做说明:
```shell
$ A=B\ C
$ echo "$A"
B C
$ echo '$A'
$A
```
在第一个`echo`命令行中,$被置于soft quote中,将不被关闭,
因此继续处理变量替换,
因此,`echo`将A的变量值输出到屏幕,也就是"B C"的结果。

在第二个`echo`命令行中,$被置于hard quote中,则被关闭,
因此,$只是一个$符号,并不会用来做变量替换处理,
因此结果是$符号后面接一个A字母:$A.

> **练习与思考:** 如下结果为何不同?

> tips: 单引号和双引号,在quoting中均被关闭了。

```shell
$ A=B\ C
$ echo '"$A"' #最外面的是单引号
"$A"
$ echo "'$A'" #最外面的是双引号
'B C'
```

------------------------------------
在CU的shell版里,我发现很多初学者的问题,
都与quoting的理解有关。
比方说,若我们在awk或sed的命令参数中,
调用之前设定的一些变量时,常会问及为何不能的问题。

要解决这些问题,关键点就是:**区分出 shell meta 与 command meta**

前面我们提到的那些meta,都是在command line中有特殊用途的,
比方说{}就是将一系列的command line置于不具名的函数中执行(可简单视为command block),
但是,awk却需要用{}来区分出awk的命令区段(BEGIN,MAIN,END).
若你在command line中如此输入:
```shell
$ awk {print $0} 1.txt
```
由于{}在shell中并没有关闭,那shell就将{print $0}视为command block,
但同时没有`;`符号作命令分隔,因此,就出现awk语法错误结果。

要解决之,可用hard quote:
```shell
awk '{print $0}'
```
上面的hard quote应好理解,就是将原来的
{、、$、}这几个shell meta关闭,
避免掉在shell中遭到处理,而完整的成为awk的参数中command meta。

> **Note:**

> awk中使用的$0 是awk中内建的field nubmer,而非awk的变量,
> awk自身的变量无需使用$.

要是理解了hard quote的功能,在来理解soft quote与escape就不难:
```shell
awk "{print \$0}" 1.txt
awk \{print \$0\} 1.txt
```
然而,若要你改变awk的$0的0值是从另一个shell变量中读进呢?
比方说:已有变量$A的值是0, 那如何在`command line`中解决
awk的$$A呢?
你可以很直接否定掉hard quote的方案:
```shell
$ awk '{print $$A}' 1.txt
```
那是因为$A的$在hard quote中是不能替换变量的。

聪明的读者(如你!),经过本章的学习,我想,你应该可以理解为
为何我们可以使用如下操作了吧:
```shell
A=0
awk "{print \$$A}" 1.txt
awk \{print\ \$$A\} 1.txt
awk '{print $'$A'}' 1.txt
awk '{print $'"$A"'}' 1.txt
```
或许,你能给出更多方案... ^_^

更多练习:
- http://bbs.chinaunix.net/forum/viewtopic.php?t=207178
一个关于read命令的小问题:
很早以前觉得很奇怪:执行read命令,然后读取用户输入给变量赋值,
但如果输入是以空格键开始的话,这空格会被忽略,比如:
```shell
read a #输入: abc
echo "$a" #只输出abc
```
原因:
变量a的值,从终端输入的值是以IFS开头,而这些IFS将被shell解释器忽略(trim)。
应该与shell解释器分词的规则有关;

```shell
read a #输入:\ \ \ abc
echo "$a" #只输出abc
```
需要将空格字符转义

> **Note:**

> IFS Internal field separators, normally space, tab, and newline (see Blank Interpretation section).
> ......
> Blank Interpretation
> After parameter and command substitution, the results of substitution
> are scanned for internal field separator characters (those found in IFS)
> and split into distinct arguments where such characters are found.
> Explicit null arguments ("" or '') are retained.
> Implicit null arguments(those resulting from parameters that have no values)
> are removed.
> (refre to: man sh)

解决思路:

1. shell command line 主要是将整行line给分解(break down)为每一个单词(word);
2. 而词与词之间的分隔符就是IFS (Internal Field Seperator)。
3. shell会对command line作处理(如替换,quoting等), 然后再按词重组。(注:别忘了这个重组特性)
4. 当你用IFS来事开头一个变量值,那shell会先整理出这个词,然后在重组command line。
5.然而,你将IFS换成其他,那shell将视你哪些space/tab为“词”,而不是IFS。那在重组时,可以得到这些词。

若你还是不理解,那来验证一下下面这个例子:
```shell
$ A=" abc"
$ echo $A
abc
$ echo "$A" #note1
abc
$ old_IFS=$IFS
$ IFS=;
$ echo $A
abc
$ IFS=$old_IFS
$ echo $A
abc
```
> **Note:**

> 1. 这里是用 soft quoting 将里面的 space 关闭,使之不是 meta(IFS),
> 而是一个literal(white space);

> 2. IFS=; 意义是将IFS设置为空字符,因为;是shell的元字符(meta);

问题二:为什么多做了几个分号,我想知道为什么会出现空格呢?
```shell
$ a=";;;test"
$ IFS=";"
$ echo $a
test
$ a=" test"
$ echo $a
test
$ IFS=" "
$ echo $a
test
```

解答:

这个问题,出在`IFS=;`上。
因为这个`;`在问题一中的command line上是一个meta,
并非`";"`符号本身。
因此,`IFS=;`是将IFS设置为 null charactor
(不是space、tab、newline)。

要不是试试下面这个代码片段:
```shell
$ old_IFS=$IFS
$ read A
;a;b;c
$ echo $A
;a;b;c
$ IFS=";" #Note2
$ echo $A
a b c
```
> **Note:**

> 要关闭`;`可用`";"`或者`';'`或者`\;`。

- http://bbs.chinaunix.net/forum/viewtopic.php?t=216729

思考问题二:文本处理:读文件时,如何保证原汁原味。
```shell
cat file | while read i
do
echo $i
done
```
文件file的行中包含若干空,经过read只保留不重复的空格。
如何才能所见即所得。

```shell
cat file | while read i
do
echo "X${i}X"
done
```
从上面的输出,可以看出read,读入是按整行读入的;
不能原汁原味的原因:

1. 如果行的起始部分有IFS之类的字符,将被忽略;
2. `echo $i`的解析过程中,首先将$i替换为字符串,
然后对echo 字符串中字符串分词,然后命令重组,输出结果;
在分词,与命令重组时,可能导致多个相邻的IFS转化为一个;

```shell
cat file | while read i
do
echo "$i"
done
```
以上代码可以解决原因2中的,command line的分词和重组导致meta字符丢失;
但仍然解决不了原因1中,read读取行时,忽略行起始的IFS meta字符。

回过头来看上面这个问题:为何要原汁原味呢?
cat命令就是原汁原味的,只是shell的read、echo导致了某些shell的meta字符丢失;

如果只是IFS meta的丢失,可以采用如下方式:
将IFS设置为null,即`IFS=;`,
在此再次重申此处`;`是shell的meta字符,而不是literal字符;
因此要使用literal的 `;`应该是`\;`
或者关闭meta 的(soft/hard) quoting的`";"`或者`';'`。

因此上述的解决方案是:
```shell
old_IFS=$IFS
IFS=; #将IFS设置为null
cat file | while read i
do
echo "$i"
done
IFS=old_IFS #恢复IFS的原始值
```
现在,回过头来看这个问题,为什么会有这个问题呢;
其本源的问题应该是没有找到解决原始问题的最合适的方法,
而是采取了一个迂回的方式来解决了问题;

因此,我们应该回到问题的本源,重新审视一下,问题的本质。
如果要精准的获取文件的内容,应该使用od或者hexdump会更好些。

##shell十三问之5:问var=value 在export前后的差在哪?
----------------------------------------------------

这次让我们暂时丢开`command line`,
先了解一下bash变量(variable)吧...

所谓的变量,就是利用一个固定的"名称"(name),
来存取一段可以变化的"值"(value)。

###1. 变量设定(set)
在bash中, 你可以用"="来设定或者重新定义变量的内容:
```shell
name=value
```

在设定变量的时候,得遵守如下规则:

- 等号左右两边不能使用分隔符号(IFS),也应避免使用shell的保留元字符(meta charactor);
- 变量的名称(name)不能使用$符号;
- 变量的名称(name)的首字符不能是数字(number)。
- 变量的名称(name)的长度不可超过256个字符。
- 变量的名称(name)及变量的值的大小写是有区别的、敏感的(case sensitive,)

如下是一些变量设定时常见的错误:
```shell
A= B #=号前后不能有IFS
1A=B #变量名称不能以数字开头
$A=B #变量的名称里有$
a=B #这跟a=b是不同的,(这不是错误,提醒windows用户)
```

如下则是可以接受的设定:
```shell
A=" B" #IFS被关闭,参考前面的quoting章节
A1=B #并非以数字开头
A=$B #$可用在变量的值内
This_Is_A_Long_Name=b #可用_连接较长的名称或值,且有大小区别;
```

###2. 变量替换(substitution)
shell 之所以强大,其中的一个因素是它可以在命令行中对变量作
替换(substitution)处理。
在命令行中使用者可以使用$符号加上变量名称(除了用=定义变量名称之外),
将变量值给替换出来,然后再重新组建命令行。

比方:
```shell
$ A=ls
$ B=la
$ C=/tmp
$ $A -$B $C
```
以上命令行的第一个`$`是`shell prompt`, 并不在命令行之内。
必须强调的是,我们所提的变量替换,只发生在`command line`上面。
(是的,请让我们再次回到命令行吧!) 仔细分析,最后那行
`command line`,不难发现在被执行前(在输入`CR`字符之前),
`$`符号对每一个变量作替换处理(将变量的值替换出来再重组命令行),
最后会得出如下命令行:
```shell
ls -la /tmp
```

还记得第二章,我请大家"务必理解"的那两句吗?
若你忘了,我这里重贴一遍:

> **Note:**

> 若从技术的细节来看,`shell`会依据`IFS`(Internal Field Seperator)
> 将`command line`所输入的文字拆解为"字段"(word/field)。
> 然后再针对特殊字符(meta)先作处理,最后重组整行`command line`。

这里的`$`就是`command line`中最经典的meta之一了,
就是作变量替换的。在日常的shell操作中,
我们常会使用`echo`命令来查看特定的变量的值,
例如:
```shell
$ echo $A -$B $C
```
我们已学过,`echo`命令只单纯将其argument送至"标准输出"(stdout, 通常是我们的屏幕)。
所以上面的命令会在屏幕上得到如下结果:
```shell
ls -al /tmp
```
这是由于`echo`命令在执行时,会先将`$A` (ls)、`$B` (la)跟`$C` (/tmp)给替换出来;
利用shell对变量的替换处理能力,我们在设定变量时就更为灵活了:
```shell
A=B
B=$A
```
这样,B的变量值就可继承A变量"当时"的变量值了。
不过,不要以"数学逻辑"来套用变量的设定,比方说:
```shell
A=B
B=C
```
这样,并不会让A的变量值变成C。再如:
```shell
A=B
B=$A
A=C
```
同样也不会让B的值变成C。

上面是单纯定义了两个不同名称的变量:
A 与 B, 它们的取值分别是C与B。

若变量被重复定义的话,则原有值为新值所取代。(这不正是"可变的量"吗?^_^)
当我们在设定变量的时候,请记住这点:**用一个名称存储一个数值**, 仅此而已。

此外, 我们也可以利用命令行的变量替换能力来"扩充"(append)变量的值:
```shell
A=B:C:D
A=$A:E

```
这样, 第一行我们设定A的值为"B:C:D",
然后,第二行再将值扩充为"B:C:D:E"。

上面的扩充的范例,我们使用分隔符号(:)来达到扩充的目的,
要是没有分隔符的话,如下是有问题的:
```shell
A=BCD
B=$AE
```
因为第二次是将A的值继承$AE的替换结果,而非$A再加E。
要解决此问题,我们可用更严谨的替换处理:
```shell
A=BCD
A=${A}E
```

上例中,我们使用{}将变量名称范围给明确定义出来,
如此一来, 我们就可以将A的变量值从BCD给扩充为BCDE。

> **Tips:**
> 关于${name}事实上还可以做到更多的变量处理能力,
> 这些均属于比较进阶阶段的变量处理,现阶段暂不介绍了,
> 请大家自行参考资料。

###3. export 变量

严格来说,我们在当前shell中所定义的变量,均属于
"本地变量"(local variable), 只有经过`export`命令的
"输出"处理,才能成为"环境变量"(environment variable):
```shell
$ A=B
$ export A
```
或者
```shell
$ export A=B
```
经过`export`输出处理之后,变量A就能成为一个环境变量
供其后的命令使用。在使用`export`的时候,请别忘记
shell在命令行对变量的"替换"(substitution)处理。
比方说:
```shell
$ A=B
$ B=C
$ export $A
```
上面的命令并未将A输出为"环境变量",而是将B导出
这是因为在这个命令行中,$A会首先被替换为B,然后在"塞回"
作`export`的参数。

要理解这个`export`,事实上需要从process(进程)的角度来理解才能透彻。
我们将于下一章为大家说明process(进程)的概念,敬请留意。

####4. 取消变量(unset)
要取消一个变量,在bash中可使用`unset`命令来处理:
```shell
unset A
```
与`export`一样,`unset`命令行,也同样会作
变量替换(这其实是shell的功能之一),
因此:
```shell
$ A=B
$ B=C
$ unset $A
```
事实上,所取消的是变量B而不是A。

此外,变量一旦经过unset取消之后,
其结果是将整个变量拿掉,而不是取消变量的值。

如下两行其实是很不一样的:
```shell
$ A=
$ unset A
```
第一行只是将变量A设定为"空值"(null value),
但第二行则是让变量A不存在。
虽然用眼睛来看,
这两种变量的状态在如下的命令结果中都是一样的:
```shell
$ A=
$ echo $A

$ unset A
$ echo $A
```
请学员务必能识别null value 与 unset的本质区别,
这在一些进阶的变量处理上是很严格的。

比方说:
```shell
$ str= #设为null
$ var=${str=expr} #定义var
$ echo $var

$ echo $str

$ unset str #取消str
$ var=${str=expr} #定义var
$ echo $var
expr
$ echo $str
expr
```
聪明的读者(yes, you!),稍加思考的话,
应该不难发现为何同样的var=${str=expr}
在str为null与unset之下的不同吧?
若你看不出来,那可能是如下原因之一:

- 你太笨了
- 不了解 var=${str=expr} 这个进阶处理
- 对本篇说明还没有来得及消化吸收
- 我讲得不好

不知,您选哪个呢?...... ^_^.

##shell十三问之6:exec跟source差在哪?
-------------------------------------

这次让我们从CU shell版的一个实例帖子来谈起吧:
(论坛改版后,原链接已经失效)

例中的提问原文如下:

> **帖子提问:**

> cd /etc/aa/bb/cc可以执行
> 但是把这条命令放入shell脚本后,shell脚本不执行!
> 这是什么原因?

意思是:运行shell脚本,并没有移动到/etc/aa/bb/cc目录。

我当时如何回答暂时别去深究,先让我们了解一下进程
(process)的概念好了。

首先,我们所执行的任何程序,都是父进程(parent process)产生的一个
子进程(child process),子进程在结束后,将返回到父进程去。
此现象在Linux中被称为`fork`。

(为何要称为fork呢? 嗯,画一下图或许比较好理解...^_^)

当子进程被产生的时候,将会从父进程那里获得一定的资源分配、及
(更重要的是)继承父进程的环境。

让我们回到上一章所谈到的"环境变量"吧:
**所谓环境变量其实就是那些会传给子进程的变量**。
简单而言, "遗传性"就是区分本地变量与环境变量的决定性指标。
然而,从遗传的角度来看,我们不难发现环境变量的另一个重要特征:
**环境变量只能从父进程到子进程单向传递。
换句话说:在子进程中环境如何变更,均不会影响父进程的环境。**

接下来,在让我们了解一下shell脚本(shell script)的概念.
所谓shell script 讲起来很简单,就是将你平时在shell prompt输入的多行
`command line`, 依序输入到一个文件文件而已。

再结合以上两个概念(process + script),那应该不难理解如下的这句话的意思了:
正常来说,当我们执行一个shell script时,其实是先产生一个sub-shell的子进程,
然后sub-shell再去产生命令行的子进程。
然则,那让我们回到本章开始时,所提到的例子在重新思考:

> **帖子提问:**

> cd /etc/aa/bb/cc可以执行
> 但是把这条命令放入shell脚本后,shell脚本不执行!
> 这是什么原因?

意思是:运行shell脚本,并没有移动到/etc/aa/bb/cc目录。

我当时的答案是这样的:

> 因为,我们一般跑的shell script是用sub-shell去执行的。
> 从process的概念来看,是 parent process产生一个child process去执行,
> 当child结束后,返回parent, 但parent的环境是不会因child的改变而改变的。
> 所谓的环境变量元数很多,如effective id(euid),variable, working dir等等...
> 其中的working dir($PWD) 正是楼主的疑问所在:
> 当用sub-shell来跑script的话,sub-shell的$pwd会因为cd而变更,
> 但返回primary shell时,$PWD是不会变更的。

能够了解问题的原因及其原理是很好的,但是?
如何解决问题,恐怕是我们更应该感兴趣的是吧?

那好,接下来,再让我们了解一下`source`命令好了。
当你有了`fork`的概念之后,要理解`soruce`就不难:

所谓`source`,就是让script在当前shell内执行、
而不是产生一个sub-shell来执行。
由于所有执行结果均在当前shell内执行、而不是产生一个sub-shell来执行。

因此, 只要我们原本单独输入的script命令行,变成`source`命令的参数,
就可轻而易举地解决前面提到的问题了。

比方说,原本我们是如此执行script的:
```shell
$ ./my_script.sh
```
现在改成这样既可:
```shell
$ source ./my_script.sh
```
或者:
```shell
$ . ./my_script.sh
```

说到这里,我想,各位有兴趣看看`/etc`底下的众多设定的文件,
应该不难理解它们被定义后,如何让其他script读取并继承了吧?

若然,日后,你有机会写自己的script,
应也不难专门指定一个设定的文件以供不同的script一起"共用"了... ^_^

okay,到这里,若你搞懂`fork`与`source`的不同,
那接下来再接受一个挑战:
> 那`exec`又与`source`/`fork`有何不同呢?

哦...要了解`exec`或许较为复杂,尤其是扯上`File Decscriptor`的话...
不过,简单来说:
> `exec` 也是让script在同一个进程上执行,但是原有进程则被结束了。
> 简言之,原有进程能否终止,就是`exec`与`source`/`fork`的最大差异了。

嗯,光是从理论去理解,或许没那么好消化,
不如动手"实践+思考"来得印象深刻哦。

下面让我们为两个简单的script,分别命名为1.sh以及2.sh

1.sh

```shell
#!/bin/bash

A=B
echo "PID for 1.sh before exec/source/fork:$$"

export A
echo "1.sh: \$A is $A"

case $1 in
exec)
echo "using exec..."
exec ./2.sh ;;
source)
echo "using source..."
. ./2.sh ;;
*)
echo "using fork by default..."
./2.sh ;;
esac

echo "PID for 1.sh after exec/source/fork:$$"
echo "1.sh: \$A is $A"
```
2.sh

```shell
#!/bin/bash

echo "PID for 2.sh: $$"
echo "2.sh get \$A=$A from 1.sh"

A=C
export A
echo "2.sh: \$A is $A"
```

然后分别跑如下参数来观察结果:
```shell
$ ./1.sh fork
$ ./1.sh source
$ ./1.sh exec
```
好了,别忘了仔细比较输出结果的不同及背后的原因哦...
若有疑问,欢迎提出来一起讨论讨论~~~~

happy scripting! ^_^

##shell十三问之7:()与{}差在哪?
--------------------------------

嗯,这次轻松一下,不讲太多... ^_^

先说一下,为何要用()或者{}好了。

许多时候,我们在shell操作上,需要在
一定的条件下执行多个命令,也就是说,
要么不执行,要么就全执行,而不是每次
依序的判断是否要执行下一个命令。

或者,要从一些命令执行的先后次序中得到结果,
如算术运算的2*(3+4)那样...

这时候,我们就可以引入"`命令群组`"(`command group`)
的概念:将许多命令集中处理。

在shell `command line`中,一般人或许不太计较`()`与
`{}`这两对符号的差异,虽然两者都可以将多个命令当作群组处理,
但若从技术细节上,却是很不一样的:

- `()` 将`command group`置于`sub-shell`(`子shell`)中去执行,也称 `nested sub-shell`。
- `{}` 则是在同一个`shell`内完成,也称`non-named command group`。

若你对上一章的fork与source的概念还记得的话,
那就不难理解两者的差异了。

要是在 `command group`中扯上变量及其他环境的修改,
我们可以根据不同的需求来使用`()`或`{}`。
通常而言, 若所作的修改是临时的,且不想影响原有或以后的设定,
那我们就使用`nested sub-shell`, 即`()`;
反之,则用`non-named command group`, 即`{}`。

是的,光从`command line`来看,`()` 与 `{}`差别就讲完了,够轻松吧~~~, ^_^

然而,这两个`meta`用在其他`command meta`或领域中(如Regular Expression),
还是有很多差别的。
只是,我不打算再去说明了,留给读者慢慢发掘好了...

我这里只想补充一个概念,就是`function`。
所谓`function`,就是用一个名字去命名一个`command group`,
然后再调用这个名字去执行`command group`。

从`non-named command group`来推断,
大概你也可以推测到我要说的是`{}`了吧?(yes! 你真聪明 ^_^)

在bash中,function的定义方式有两种:

- 方式一:
```shell
function function_name {
command1
command2
command3
.....
}
```
- 方式二:
```shell
function_name () {
command1
command2
command3
......
}
```

用哪一种方式无所谓,
只是碰到所定义的名称与现有的命令或者别名冲突的话,
方式二或许会失败。
但方式二起码可以少打个`function`这一串英文字符,
对懒人来说(如我),有何乐而不为呢?...^_^

`function` 在一定程度上来说,也可以称为"函数",
但请不要与传统编程所使用的"函数"(library)搞混了,
毕竟两者差异很大。
唯一相同的是,我们都可以随时用"已定义的名称"来调用它们...

若我们在shell操作中,需要不断地重复某些命令,
我们首先想到的,或许是将命令写成shell脚本(shell script)。
不过,我们也可以写成function,
然后在command line中打上function_name就可当一般的shell script使用了。

若只是你在shell中定义的`function`,
除了用`unset` function_name取消外,
一旦你退出shell, function也跟着消失。
然而,在script中使用function却有许多好处,
除了提高整体script的执行性能外(因为已经载入),
还可以节省许多重复的代码......

简单而言,若你会将多个命令写成script以供调用的话,
那你可以将function看成script中script。... ^_^

而且通过上一章节介绍的`source`命令,
我们可以自行定义许许多多好用的function,
在集中写在特定文件中,
然后,在其他的script中用`source`将它们载入,并反复执行。

若你是`RedHat Linux`的使用者,
或许,已经猜出 `/etc/rc.d/init.d/functions`这个文件时啥作用了~~~ ^_^

okay,说要轻松点的嘛,那这次就暂时写到这吧。
祝大家学习愉快,^_^

##shell十三问之8: $(())与$()还有${}差在哪?
--------------------------------------------

我们上一章介绍了()与{}的不同,
这次让我们扩展一下,看看更多的变化:
$()与${}又是啥玩意儿呢?

在bash shell中, `$()`与\`\`(反引号)都是用来做
`命令替换`(command substitution)的。

所谓的`命令替换`与我们第五章学过的变量替换差不多,
都是用来`重组命令行`:
完成 \`\` 或者`$()`里面的
命令,将其结果替换出来,
再重组命令行。

例如:
```shell
$ echo the last sunday is $(date -d "last sunday" +%Y-%m-%d)
```
如此便可方便得到上一个星期天的日期了...^_^

在操作上, 用$()或\`\`都无所谓,
只是我个人比较喜欢用$(),理由是:

1. \`\`(反引号)很容易与''(单引号)搞混乱,尤其对初学者来说。
有时在一些奇怪的字形显示中,两种符号是一模一样的(只取两点)。
当然了有经验的朋友还是一眼就能分辨两者。只是,若能更好的避免混乱,
又何乐而不为呢? ^_^

2. 在多次的复合替换中, \`\`需要额外的转义(escape, \)处理,而$()则比较直观。
例如,一个错误的使用的例子:
```shell
command1 `command2 `command3` `
```
原来的本意是要在command2 \`command3\` ,
先将command3替换出来给command2处理,
然后再将command2的处理结果,给command1来处理。
然而真正的结果在命令行中却是分成了\`command2\`与 \`\`。

正确的输入应该如下:
```shell
command1 `command2 \`command3\` `
```
要不然换成$()就没有问题了:

```shell
command1 $(commmand2 $(command3))
```

只要你喜欢,做多少层的替换都没有问题~~~^_^

不过,$()并不是没有弊端的...
首先,\`\`基本上可用在所有的unix shell中使用,
若写成 shell script,其移植性比较高。
而$()并不是每一种shell都能使用,我只能说,
若你用bash2的话,肯定没问题... ^_^

接下来,再让我们看看${}吧...它其实就是用来做
变量替换用的啦。
一般情况下,$var与${var}并没有啥不一样。
但是用${}会比较精准的界定变量名称的范围,
比方说:
```shell
$ A=B
$ echo $AB
```
原本是打算先将$A的结果替换出来,
然后在其后补一个字母B;
但命令行上,
真正的结果却是替换变量名称为AB的值出来...
若使用${}就没有问题了:
```shell
$ A=B
$ echo ${A}B
$ BB
```
不过,假如你只看到`${}`只能用来界定变量名称的话,
那你就实在太小看bash了。

为了完整起见,我这里再用一些例子加以说明`${}`的一些
特异功能:
假设我们定义了一个变量file为:
```shell
file=/dir1/dir2/dir3/my.file.txt
```
我们可以用`${}`分别替换获得不同的值:

####1. shell字符串的非贪婪(最小匹配)左删除
-----------

```shell
${file#*/} #其值为:dir1/dir2/dir3/my.file.txt
```
拿掉第一个`/`及其左边的字符串,其结果为:
`dir1/dir2/dir3/my.file.txt` 。
```shell
${file#*.} #其值为:file.txt
```
拿掉第一个`.`及其左边的字符串,其结果为:
`file.txt` 。

####2. shell字符串的贪婪(最大匹配)左删除:
------
```shell
${file##*/} #其值为:my.file.txt
```
拿掉最后一个`/`及其左边的字符串,其结果为:
`my.file.txt`

```shell
${file##*.} #其值为:txt
```
拿掉最后一个`.`及其左边的字符串,其结果为:
`txt`

####3. shell字符串的非贪婪(最小匹配)右删除:
----------
```shell
${file%/*} #其值为:/dir1/dir2/dir3
```
拿掉最后一个`/`及其右边的字符串,其结果为:
`/dir1/dir2/dir3`。

```shell
${file%.*} #其值为:/dir1/dir2/dir3/my.file
```
拿掉最后一个`.`及其右边的字符串,其结果为:
`/dir1/dir2/dir3/my.file`。

####4. shell字符串的贪婪(最大匹配)右删除:
-----
```shell
${file%%/*} #其值为:其值为空。
```
拿掉第一个`/`及其右边的字符串,其结果为:
空串。

```shell
${file%%.*} #其值为:/dir1/dir2/dir3/my。
```
拿掉第一个`.`及其右边的字符串,其结果为:
/dir1/dir2/dir3/my。

> **Tips:**

> 记忆方法:

> `#`是去掉左边(在键盘上`#`在`$`的左边);

> `%`是去掉右边(在键盘上`%`在`$`的右边);

> 单个符号是最小匹配;

> 两个符号是最大匹配;

####5. shell字符串取子串:
----

```shell
${file:0:5} #提取最左边的5个字符:/dir1
${file:5:5} #提取第5个字符及其右边的5个字符:/dir2
```
shell字符串取子串的格式:`${s:pos:length}`,
取字符串s的子串:从pos位置开始的字符(包括该字符)的长度为length的的子串;
其中pos为子串的首字符,在s中位置;
length为子串的长度;

> **Note:** 字符串中字符的起始编号为0.

####6. shell字符串变量值的替换:
-----

```shell
${file/dir/path} #将第一个dir替换为path:/path1/dir2/dir3/my.file.txt
${file//dir/path} #将全部的dir替换为path:/path1/path2/path3/my.file.txt
```

shell字符串变量值的替换格式:

- 首次替换:
`${s/src_pattern/dst_pattern}` 将字符串s中的第一个src_pattern替换为dst_pattern。

- 全部替换:
`${s//src_pattern/dst_pattern}` 将字符串s中的所有出现的src_pattern替换为dst_pattern.

####7. ${}还可针对变量的不同状态(没设定、空值、非空值)进行赋值:
------

- `${file-my.file.txt}` #如果file没有设定,则使用
使用my.file.txt作为返回值, 否则返回${file};(空值及非空值时,不作处理。);

- `${file:-my.file.txt}` #如果file没有设定或者${file}为空值, 均使用my.file.txt作为其返回值,否则,返回${file}.(${file} 为非空值时,不作处理);

- `${file+my.file.txt}` #如果file已设定(为空值或非空值), 则使用my.file.txt作为其返回值,否则不作处理。(未设定时,不作处理);

- `${file:+my.file.txt}` #如果${file}为非空值, 则使用my.file.txt作为其返回值,否则,(未设定或者为空值时)不作处理。

- `${file=my.file.txt}` #如果file为设定,则将file赋值为my.file.txt,同时将${file}作为其返回值;否则,file已设定(为空值或非空值),则返回${file}。

- `${file:=my.file.txt}` #如果file未设定或者${file}为空值, 则my.file.txt作为其返回值,
同时,将${file}赋值为my.file.txt,否则,(非空值时)不作处理。

- `${file?my.file.txt}` #如果file没有设定,则将my.file.txt输出至STDERR, 否侧,
已设定(空值与非空值时),不作处理。

- `${file:?my.file.txt}` #若果file未设定或者为空值,则将my.file.txt输出至STDERR,否则,
非空值时,不作任何处理。

> **Tips:**

> 以上的理解在于,你一定要分清楚,`unset`与`null`以及non-null这三种状态的赋值;
>一般而言,与null有关,若不带`:`, null不受影响;
> 若带 `:`, 则连null值也受影响。

####8. 计算shell字符串变量的长度:`${#var}`
--------------
```shell
${#file} #其值为27, 因为/dir1/dir2/dir3/my.file.txt刚好为27个字符。
```

####9. bash数组(array)的处理方法
-------------------

接下来,为大家介绍一下bash的数组(array)的处理方法。
一般而言, `A="a b c def"`
这样的变量只是将`$A`替换为一个字符串,
但是改为 `A=(a b c def)`,
则是将`$A`定义为数组....

#####1). 数组替换方法可参考如下方法:

```shell
${A[@]} #方法一
${A[*]} #方法二
```
以上两种方法均可以得到:a b c def, 即数组的全部元素。

#####2). 访问数组的成员:
```shell
${A[0]}
```
其中,`${A[0]}`可得到a, 即数组A的第一个元素,
而 `${A[1]}`则为数组A的第二元素,依次类推。

#####3). 数组的length:
```shell
${#A[@]} #方法一
${#A[*]} #方法二
```
以上两种方法均可以得到数组的长度: 4, 即数组的所有元素的个数。

回忆一下,针对字符串的长度计算,使用`${#str_var}`;
我们同样可以将该方法应用于数组的成员:
```shell
${#A[0]}
```
其中,`${#A[0]}`可以得到:1,即数组A的第一个元素(a)的长度;
同理,`${#A[3]}`可以得到: 3, 即数组A的第4个元素(def)的长度。

#####4). 数组元素的重新赋值:
```shell
A[3]=xyz
```
将数组A的第四个元素重新定义为xyz。

> **Tips:**

> 诸如此类的...

> 能够善用bash的$()与${}可以大大提高及
> 简化shell在变量上的处理能力哦~~~^_^

####10. $(())作用:
----
好了,最后为大家介绍`$(())`的用途吧:
**`$(())`是用来作整数运算的**。

在bash中, `$(())`的整数运算符号大致有这些:

- \+\- * / #分别为"加、减、乘、除"。
- % #余数运算,(模数运算)
- & | ^ ! #分别为"AND、OR、XOR、NOT"运算。

例如:
```shell
$ a=5; b=7; c=2;
$ echo $(( a + b * c ))
19
$ echo $(( (a + b)/c ))
6
$ echo $(( (a * b) % c ))
1
```
在`$(())`中的变量名称,
可以在其前面加 `$`符号来替换,
也可以不用,如:
`$(( $a + $b * $c ))` 也可以得到19的结果。

此外,`$(())`还可作不同进制(如二进制、八进制、十六进制)的运算,
只是输出结果均为十进制的。
```shell
echo $(( 16#2a )) #输出结果为:42,(16进制的2a)
```
以一个实用的例子来看看吧 :
假如当前的umask是022,那么新建文件的权限即为:
```shell
$ umask 022
$ echo "obase=8; $(( 8#666 & (8#777 ^ 8#$(umask)) ))" | bc
644
```
事实上,单纯用`(())`也可以重定义变量值,或作testing:
```shell
a=5; ((a++)) #可将$a 重定义为6
a=5; ((a--)) #可将$a 重定义为4
a=5; b=7; ((a< b)) #会得到0 (true)返回值。
```
常见的用于`(())`的测试符号有如下这些:

|符号|符号名称|
|----|--------|
| < | 小于号 |
| > | 大于号 |
| <= | 小于或等于|
| >= | 大于或等于|
| == | 等于|
| != | 不等于|

> **Note:**

> 使用`(())`作整数测试时,
> 请不要跟`[]`的整数测试搞混乱了。

> 更多的测试,我们将于第10章为大家介绍。

怎样? 好玩吧... ^_^

okay,这次暂时说这么多...

上面的介绍,并没有详列每一种可用的状态,
更多的,就请读者参考手册文件(man)吧...

##shell十三问之9:$@与$*差在哪?
------

要说$@与$*之前,
需得先从shell script的positional parameter谈起...

我们都已经知道变量(variable)是如何定义和替换的,
这个不再多讲了。

#### 1. shell script的positional parameter
--------------

但是,我们还需要知道有些变量是shell内定的,
且其名称是我们不能随意修改的。
其中,就有positional parameter在内。

在shell script中,我们可用$0, $1, $2, $3 ...
这样的变量分别提取命令行中的如下部分:
```shell
script_name parameter1 parameter2 parameter3 ...
```

我们很容易就能猜出, `$0`就是代表 shell script名称(路径)本身,
而`$1`就是其后的第一个参数,如此类推...

须得留意的是`IFS`的作用, 也就是`IFS`被quoting处理后,
那么positional parameter也会改变。

如下例:
```shell
my.sh p1 "p2 p3" p4
```
由于p2与p3之间的空白键被soft quoting所关闭了,
因此,my.sh的中$2是"p2 p3",而$3则是p4...

还记得前两章,我们提到function时,
我们不是说过,它是script中的script吗?^_^

是的,function一样可以读取自己的(有别于script的)
positional parameter, 唯一例外的是$0而已。

举例而言:
假设my.sh里有一个函数(function)叫my_fun,
若在script中跑`my_fun fp1 fp2 fp3`,
那么,function内的$0就是my.sh,而$1是fp1而不是p1了...

不如写个简单的my.sh script 看看吧:
```shell
#!/bin/bash

my_fun() {
echo '$0 inside function is '$0
echo '$1 inside function is '$1
echo '$2 inside function is '$2
}

echo '$0 outside function is '$0
echo '$1 outside function is '$1
echo '$2 outside function is '$2

my_fun fp1 "fp2 fp3"
```
然后在command line中跑一下 script就知道了:
```shell
chmod 755 my.sh

./my.sh p1 "p2 p3"
$0 outside function is ./my.sh
$1 outside function is p1
$2 outside function is p2 p3
$0 inside function is ./my.sh
$1 inside function is fp1
$2 inside function is fp2 fp3
```

然而,在使用positional parameter的时候,
我们要注意一些陷阱哦:

**$10不是替换第10个参数,
而是替换第一个参数,然后在补一个0于其后;**

也就是说, `my.sh one two three four five six seven eight nine ten`
这样的command line, my.sh里的$10不是ten而是one0 哦...小心小心
要抓到ten的话,有两种方法:

- 方法一:使用我们上一章介绍的${}, 也就是用${10}即可。

- 方法二:就是shift了。

用通俗的说法来说,
**所谓的shift就是取消positional parameter中最左边的参数($0不受影响)**。
其预设值为1,也就是shift 或shift 1 都是取消$1,
而原本的$2则变成$1, $3则变成$2...
那亲爱的读者,你说要shift掉多少个参数,
才可用$1取得到${10} 呢? ^_^

okay,当我们对positional parameter有了基本的概念之后,
那再让我们看看其他相关变量吧。

#### 2. shell script的positional parameter的number
--------------

先是$#, 它可抓出positional parameter的数量。
以前面的`my.sh p1 "p2 p3"`为例:
由于"p2 p3"之间的`IFS`是在soft quote中,
因此,$#就可得到的值是2.
但如果p2与p3没有置于quoting中话,
那$#就可得到3的值了。
同样的规则,在function中也是一样。

因此,我们常在shell script里用如下方法,
测试script是否有读进参数:
```shell
[ $# = 0 ]
```
假如为0, 那就表示script没有参数,否则就是带有参数...

#### 3. shell script中的$@与$*
-------------

接下来就是**$@与$*:
精确来讲,两者只有在soft quote中才有差异,
否则,都表示“全部参数” ($0除外)**。

若在comamnd line上, 跑`my.sh p1 "p2 p3" p4`的话,
不管$@还是$\*, 都可得到 p1 p2 p3 p4就是了。

但是,**如果置于soft quote中的话:**

- **"$@"则可得到 "p1" "p2 p3" "p4" 这三个不同字段(word);**
- **"$*"则可得到 "p1 p2 p3 p4" 这一整个单一的字段。**

我们修改一下前面的my.sh,使之内容如下:
```shell
#!/bin/bash

my_fun() {
echo "$#"
}

echo 'the number of parameter in "$@" is ' $(my_fun "$@")
echo 'the number of parameter in "$*" is ' $(my_fun "$*")

```
然后再执行:
```shell
./my.sh p1 "p2 p3" p4
```
就知道,$@与$*差在哪了... ^_^

##shell十三问之10:&& 与 || 差在哪?
------------------------------------

好不容易,进入了两位数的章节了...
一路走来,很辛苦吧?也很快乐吧? ^_^

在解答本章题目之前,先让我们了解一个概念:
return value。

我们在shell下跑的每一个command或function,
在结束的时候都会传回父进程一个值,称为 `return value`。

在shell command line中可用`$?`,
这个变量得到最"新"的一个`return value`,
也就是刚刚结束的那个进程传回的值。

`Return Value`(RV)的取值为0-255之间,
由进程或者script的作者自行定义:

- 若在script里,用exit RV 来指定其值;
若没有指定, 在结束时,以最后一个命令的RV,为script的RV值。

- 若在function里,则用return RV 来代替exit RV即可。

**`Return Value`的作用:用来判断进程的退出状态(exit status)**.
进程的退出状态有两种:

- 0值为"真"(true)
- 非0值为"假"(false)

举个例子来说明好了:
假设当前目录内有一个my.file的文件, 而no.file是不存在的:
```shell
$ touch my.file
$ ls my.file
$ echo $? #first echo
0
$ ls no.file
ls: no.file: No such file or directory
$ echo $? #second echo
1
$ echo $? #third echo
0
```
上例的:

- 第一个echo是关于`ls my.file`的RV,可得到0的值,因此为true。
- 第二个echo是关于`ls no.file`的RV,得到非0的值,因此为false。
- 第三个echo是关于`echo $?`的RV,得到0值, 因此为true。

请记住:
每一个command在结束时,都会返回`return value`,不管你跑什么命令...
然而,有一个命令却是“专门”用来测试某一条而返回`return value`,
以供true或false的判断, 它就是`test`命令。

若你用的是bash, 请在command line下,
打`man test`,或者 `man bash` 来了解这个`test`的用法。
这是你可用作参考的最精准的文件了,要是听别人说的,仅作参考就好...

下面,我只简单作一些辅助说明,其余的一律以 `man`为准:
首先,`test`的表达式,我们称为expression,其命令格式有两种:
```shell
test expression
```
或者
```shell
[ expression ]
```
> **Note:**

> 请务必注意 `[]` 之间的空白键!

用哪一种格式无所谓,都是一样的效果。
(我个人比较喜欢后者...)

其次,bash的`test`目前支持的测试对象只有三种:

- string:字符串,也就是纯文字。
- integer:整数(0或正整数、不含负数或小数)
- file: 文件

请初学者,一定要搞清楚这三者的差异,
因为`test`所使用的expression是不一样的。

以A=123这个变量为例:

- `[ "$A" = 123 ]` #是字符串测试,测试$A是不是1、2、3这三个字符。

- `[ "$A" -eq 123 ]` #是整数测试,以测试$A是否等于123.

- `[-e "$A" ]` #文件测试,测试123这份文件是否存在.

第三,
当expression测试为“真”时, `test`就返回0(true)的`return value`;
否则,返回非0(false).

若在 expression 之前加一个`!`(感叹号),则在expression为假时,return value为0,
否则, return value 为非0值。

同时,`test`也允许多重复合测试:

- expression1 -a expression2 #当两个expression都为true,返回0,否则,返回非0;
- expression1 -o expression2 #当两个expression均为false时,返回非0,否则,返回0;

例如:
```shell
[ -d "$file" -a -x "$file" ]
```
表示当$file是一个目录,且同时具有x权限时,test才会为true。

第四,在command line中使用`test`时,请别忘记命令行的“重组”特性,
也就是在碰到meta时,会先处理meta,在重新组建命令行。
(这个概念在第2章和第4章进行了反复强调)

比方说, 若`test`碰到变量或者命令替换时,
若不能满足 expression的格式时,将会得到语法错误的结果。

举例来说好了:

关于`[ string1 = string2 ]`这个test格式,
在等号两边必须要有字符串,其中包括空串(null串,可用soft quote或者hard quote取得)。

假如$A目前没有定义,或被定义为空字符串的话,
那如下的用法将会失败:
```shell
$ unset A
$ [ $A = abc ]
[: =: unary oprator expected
```
这是因为命令行碰到$这个meta时,会替换$A的值,
然后,再重组命令行,那就变成了:
`[ = abc ]`,
如此一来,=的左边就没有字符串存在了,
因此,造成test的语法错误。
但是,下面这个写法则是成立的。

```shell
$ [ "$A" = abc ]
$ echo $?
1
```
这是因为命令行重组后的结果为:
`[ "" = abc ]`,
由于等号的左边我们用soft quote得到一个空串,
而让test的语法得以通过...

读者诸君,请务必留意这些细节哦,
因为稍一不慎,将会导致`test`的结果变了个样。
若您对`test`还不是很有经验的话,
那在使用test时,不妨先采用如下这一个"法则":

** 若在`test`中碰到变量替换,用soft quote是最保险的***。

若你对quoting不熟的话,请重新温习第四章的内容吧...^_^

okay, 关于更多的`test`的用法,老话一句:请看其man page (`man test`)吧!^_^

虽然洋洋洒洒读了一大堆,或许你还在嘀咕...那...那个`return value`有啥用?

问得好:
告诉你:return value的作用可大了,
若你想要你的shell变"聪明"的话,就全靠它了:
有了return value, 我们可以让shell根据不同的状态做不同的事情...

这时候,才让我来揭晓本章的答案吧~~~~^_^

`&&` 与 `||` 都是用来"组建" 多个command line用的;

- `command1 && command2` # command2只有在command1的RV为0(true)的条件下执行。
- `command1 || command2` # command2 只有在command1的RV为非0(false)的条件下执行。

以例子来说好了:
```shell
$ A=123
$ [ -n "$A" ] && echo "yes! it's true."
yes! it's true.
$ unset A
$ [ -n "$A" ] && echo "yes! it's true."
$ [ -n "$A" ] || echo "no, it's Not true."
no, it's Not true
```

> **Note:**

> `[ -n string ]`是测试string长度大于0, 则为true。

上例中,第一个`&&`命令之所以会执行其右边的`echo`命令,
是因为上一个`test`返回了0的RV值;
但第二个,就不会执行,因为`test`返回了非0的结果...
同理,`||`右边的`echo`会被执行,却正是因为左边的`test`返回非0所引起的。

事实上,我们在同一个命令行中,可用多个`&&` 或 `||` 来组建呢。
```shell
$ A=123
$ [ -n "$A" ] && echo "yes! it's true." || echo "no, it's Not ture."
yes! it's true.
$ unset A
$ [ -n "$A" ] && echo "yes! it's true." || echo "no, it's Not ture."
no, it's Not true
```

怎样,从这一刻开始,你是否觉得我们的shell是“很聪明”的呢? ^_^

好了,最后布置一道练习题给大家做做看:
下面的判断是:当$A被赋值时,在看看其是否小于100,否则输出too big!
```shell
$ A=123
$ [ -n "$A" ] && [ "$A" -lt 100 ] || echo 'too big!'
$ too big!
```
若我取消A,照理说,应该不会输出文字啊,(因为第一个条件不成立)。
```shell
$ unset A
$ [ -n "$A" ] && [ "$A" -lt 100 ] || echo 'too big!'
$ too big!
```
为何上面的结果也可得到呢?
又如何解决呢?

> **Tips:**

>修改的方法有很多种,
>其中一种方法可以利用第7章中介绍过 `command group`...

快告诉我答案,其余免谈....

解决方法1:`sub-shell`:
```shell
$ unset A
$ [ -n "$A" ] && ( [ "$A" -lt 100 ] || echo 'too big!' )
```
解决方法二:`command group`:
```shell
$ unset A
$ [ -n "$A" ] && { [ "$A" -lt 100 ] || echo 'too big!'}
```

##shell十三问之11:>与< 差在哪?
----------------

这次的题目,之前我在CU的shell版说明过了:
(原帖的连接在论坛改版后,已经失效)
这次我就不重写了,将帖子的内容“抄”下来就是了...

#### 1. 文件描述符(fd, File Descriptor)
----------
谈到`I/O redirection`,不妨先让我们认识一下`File Descriptor`(`fd`,文件描述符)。

进程的运算,在大部分情况下,都是进行数据(data)的处理,
这些数据从哪里,读进来?又输出到哪里呢?
这就是file descriptor(fd)的功用了。

在shell的进程中,最常使用的`fd`大概有三个,分别为:

- 0:standard Input (`STDIN`)
- 1: standard output(`STDOUT`)
- 2: standard Error output (`STDERR`)

在标准情况下,这些fd分别跟如下设备(device)关联:

- `stdin`(0): keyboard
- `stdout`(1): monitor
- `stderr`(2): monitor

> **Tips:**
> linux中的文件描述符(fd)用整数表示。
> linux中任何一个进程都默认打开三个文件,
> 这三个文件对应的文件描述符分别是:0, 1, 2;
> 即stdin, stdout, stderr.

我们可以用如下命令测试一下:
```shell
$ mail -s test root
this is a test mail。
please skip.
^d (同时按下ctrl 跟d键)
```

很明显,`mail`进程所读进的数据,就是从
`stdin` 也就是keyboard读进的。
不过,不见得每个进程的`stdin`都跟`mail`一样
从`keyboard`读进,因为进程的作者可以从文件参数读进`stdin`,
如:
```shell
$ cat /etc/passwd
```

但,要是`cat`之后没有文件参数则如何呢?
哦, 请你自己玩玩看...^_^
```shell
$ cat
```

> **Tips:**

> 请留意数据输出到哪里去了,
> 最后别忘了按`ctrl+d`(`^d`), 退出stdin输入。

至于`stdout`与`stderr`,嗯...等我有空再续吧...^_^
还是,有哪位前辈来玩接龙呢?

相信,经过上一个练习后,
你对`stdin`与`stdout`应该不难理解了吧?
然后,让我们看看`stderr`好了。

事实上,`stderr`没什么难理解的:
说白了就是“错误信息”要往哪里输出而已...
比方说, 若读进的文件参数不存在的,
那我们在monitor上就看到了:

```shell
$ ls no.such.file
ls: no.such.file: No such file or directory
```

若同一个命令,同时成生`stdout`与`stderr`呢?
那还不简单,都送到monitor来就好了:
```shell
$ touch my.file
$ ls my.file on.such.file
ls: no.such.file: No such file or directory
my.file
```

okay, 至此,关于fd及其名称、还有相关联的设备,
相信你已经没问题了吧?

---------------------------------------
#### 2. I/O 重定向(I/O Redirection)
---------------------------------------

那好,接下来让我们看看如何改变这些fd的预设数据通道。

- 用`<` 来改变读进的数据通道(stdin),使之从指定的文件读进。
- 用`>` 来改变输出的数据通道(stdout,stderr),使之输出到指定的文件。

--------
#####2.1 输入重定向`n<`(input redirection)
--------
比方说:
```shell
$ cat < my.file
```
就是从my.file读入数据

```shell
$ mail -s test root < /etc/passwd
```
则是从/etc/passwd读入...

这样一来,stdin将不再是从keyboard读入,
而是从指定的文件读入了...

严格来说,**`<`符号之前需要指定一个fd的(之前不能有空白),但因为0是`<`的预设值,因此,`<`与`0<`是一样的***。

okay,这样好理解了吧?

那要是用两个`<`,即`<<`又是啥呢?
**这是所谓的`here document`,
它可以让我们输入一段文本,
直到读到`<<` 后指定的字符串**。

比方说:
```shell
$ cat <n`(output redirection)
-------------

当你搞懂了`0<` 原来就是改变`stdin`的数据输入通道之后,
相信要理解如下两个redirection就不难了:

- `1>` #改变stdout的输出通道;
- `2>` #改变stderr的输出通道;

两者都是将原来输出到monitor的数据,
重定向输出到指定的文件了。

**由于1是`>`的预设值,
因此,`1>`与`>`是相同的,都是改变`stdout`**.

用上次的ls的例子说明一下好了:
```shell
$ ls my.file no.such.file 1>file.out
ls: no.such.file: No such file or directory
```
这样monitor的输出就只剩下`stderr`的输出了,
因为`stdout`重定向输出到文件file.out去了。

```shell
$ ls my.file no.such.file 2>file.err
my.file
```
这样monitor就只剩下了`stdout`,
因为`stderr`重定向输出到文件file.err了。

```shell
$ ls my.file no.such.file 1>file.out 2>file.err

```

这样monitor就啥也没有了,
因为`stdout`与`stderr`都重定向输出到文件了。

呵呵,看来要理解`>`一点也不难啦是不? 没骗你吧? ^_^
**不过有些地方还是要注意一下的**。

```shell
$ ls my.file no.such.file 1>file.both 2>file.both

```

假如`stdout`(1)与`stderr`(2)都同时在写入file.both的话,
则是采取"覆盖"的方式:后来写入覆盖前面的。

让我们假设一个`stdout`与`stderr`同时写入到file.out的情形好了;

- 首先`stdout`写入10个字符
- 然后`stderr`写入6个字符

那么,这时原本的`stdout`输出的10个字符,
将被`stderr`输出的6个字符覆盖掉了。

那如何解决呢?所谓山不转路转,路不转人转嘛,
我们可以换一个思维:
将`stderr`导进`stdout`
或者将`stdout`导进到`stderr`,
而不是大家在抢同一份文件,不就行了。
bingo就是这样啦:

- 2>&1 #将`stderr`并进`stdout`输出
- 1>&2 或者 >&2 #将`stdout`并进`stderr`输出。

于是,前面的错误操作可以改写为:
```shell
$ ls my.file no.such.file 1>file.both 2>&1
$ ls my.file no.such.file 2>file.both >&2
```
这样,不就皆大欢喜了吗? ~~~ ^_^

不过,光解决了同时写入的问题还不够,
我们还有其他技巧需要了解的。
故事还没有结束,别走开广告后,我们在回来....

------
#####2.3 I/O重定向与linux中的`/dev/null`
------
okay,这次不讲I/O Redirection, 请佛吧...
(有没有搞错?`网中人`是否头壳烧坏了?...)嘻~~~^_^

学佛的最高境界,就是"四大皆空"。
至于是空哪四大块,我也不知,因为我还没有到那个境界..
这个“空”字,却非常值得反复把玩:
---色即是空,空即是色
好了,施主要是能够领会"空"的禅意,那离修成正果不远了。

在linux的文件系统中,有个设备文件: `/dev/null`.
许多人都问过我,那是什么玩意儿?
我跟你说好了,那就是"空"啦。

没错空空如也的空就是null了...
请问施主是否忽然有所顿悟了呢?
然则恭喜了。

这个null在 I/O Redirection中可有用的很呢?

- 将fd `1`跟fd `2`重定向到/dev/null去,就可忽略stdout, stderr的输出。
- 将fd `0`重定向到/dev/null,那就是读进空(nothing).

比方说,我们在执行一个进程时,会同时输出到stdout与stderr,
假如你不想看到stderr(也不想存到文件), 那就可以:
```shell
$ ls my.file no.such.file 2>/dev/null
my.file
```

若要相反:只想看到stderr呢?
还不简单将stdout,重定向的/dev/null就行:
```shell
$ ls my.file no.such.file >/dev/null
ls: no.such.file: No such file or directory
```

那接下来,假如单纯的只跑进程,而不想看到任何输出呢?
哦,这里留了一手,上次没讲的法子,专门赠与有缘人... ^_^
除了用 `>/dev/null 2>&1`之外,你还可以如此:
```shell
$ ls my.file no.such.file &>/dev/null
```

> **Tips:**

>将&>换成>&也行!

------
#####2.4 重定向输出append (`>>`)
-------
okay? 请完佛,接下来,再让我们看看如下情况:
```shell
$ echo "1" > file.out
$ cat file.out
1
$ echo "2" > file.out
$ cat file.out
2
```
看来,我们在重定向stdout或stderr进一个文件时,
似乎永远只能获得最后一次的重定向的结果.
那之前的内容呢?

呵呵,要解决这个问题,很简单啦,将`>`换成`>>` 就好了;
```shell
$ echo "3" >> file.out
$ cat file.out
2
3
```
如此一来,被重定向的文件的之前的内容并不会丢失,
而新的内容则一直追加在最后面去。so easy?...

但是,只要你再次使用`>`来重定向输出的话,
那么,原来文件的内容被truncated(清洗掉)。
这是,你要如何避免呢?
----备份, yes,我听到了,不过,还有更好的吗?
既然与施主这么有缘分,老衲就送你一个锦囊妙法吧:
```shell
$ set -o noclobber
$ echo "4" > file.out
-bash:file: cannot overwrite existing file.
```

那,要如何取消这个限制呢?
哦,将`set -o `换成 `set +o`就行了:
```shell
$ set +o noclobber
$ echo "5" > file.out
$ cat file.out
5
```

再问:那有办法不取消而又“临时”改写目标文件吗?
哦,佛曰:不可告也。
啊,~~~开玩笑的,开玩笑啦~~~^_^,
哎,早就料到人心是不足的了
```shell
$ set -o noclobber
$ echo "6" >| file.out
$ cat file.out
6
```
留意到没有:
**在`>`后面加个`|`就好,
注意: `>`与`|`之间不能有空白哦**...

-----
#####2.5 I/O Redirection的优先级
-----
呼....(深呼吸吐纳一下吧)~~~ ^_^
再来还有一个难题要你去参透呢:
```shell
$ echo "some text here" >file
$ cat < file
some text here
$cat < file >file.bak
$cat < file.bak
some text here
$cat < file >file
```
嗯?注意到没有?
---怎么最后那个cat命令看到file是空的呢?
why? why? why?

前面提到:`$cat < file > file`之后,
原本有内容的文件,结果却被清空了。
要理解这个现象其实不难,
这只是priority的问题而已:
** 在IO Redirection中, stdout与stderr的管道先准备好,
才会从stdin读入数据。**
也就是说,在上例中,`>file`会将file清空,
然后才读入 `< file`。
但这时候文件的内容已被清空了,因此就变成了读不进任何数据。

哦,~~~原来如此~~~^_^
那...如下两例又如何呢?
```shell
$ cat <> file
$ cat < file >>file
```
嗯...同学们,这两个答案就当练习题喽,
下课前交作业。

> **Tips:**
> 我们了解到`>file`能够快速把文件file清空;
> 或者使用`:>file`同样可以清空文件,
>`:>file`与`>file`的功能:
>若文件file存在,则将file清空; 否则,创建空文件file (等效于`touch file`);
>二者的差别在于`>file`的方式不一定在所有的shell的都可用。

> `exec 5<>file; echo "abcd" >&5; cat <&5`
>将file文件的输入、输出定向到文件描述符5,
>从而描述符5可以接管file的输入输出;
>因此,`cat <>file`等价于`cat < file`。
>
>而`cat < file >>file`则使file内容成几何级数增长。

好了, I/O Redirection也快讲完了,
sorry,因为我也只知道这么多而已啦~~~嘻~~~^_^
不过,还有一样东东是一定要讲的,各位观众(请自行配乐~!#@$%):
就是`pipe line`也。

--------------
#####2.6 管道(pipe line)
-------------
谈到`pipe line`,我相信不少人都不会陌生:
我们在很多command line上常看到`|`符号就是pipe line了。

不过,pipe line究竟是什么东东呢?
别急别急...先查一下英文字典,看看pipe是什么意思?
没错他就是“水管”的意思...
那么,你能想象一下水管是怎样一个根接一根的吗?
又, 每根水管之间的input跟output又如何呢?
灵光一闪:原来pipe line的I/O跟水管的I/O是一模一样的:
**上一个命令的stdout接到下一个命令的stdin去了**
的确如此。不管在command line上使用了多少个pipe line,
前后两个command的I/O是彼此连接的
(恭喜:你终于开放了 ^_^ )

不过...然而...但是... ...stderr呢?
好问题不过也容易理解:
若水管漏水怎么办?
也就是说:在pipe line之间,
前一个命令的stderr是不会接进下一个命令的stdin的,
其输出,若不用2>file的话,其输出在monitor上来。
这点请你在pipe line运用上务必要注意的。

那,或许你有会问:
**有办法将stderr也喂进下一个命令的stdin吗?**
(贪得无厌的家伙),方法当然是有的,而且,你早已学习过了。
提示一下就好:**请问你如何将stderr合并进stdout一同输出呢?
若你答不出来,下课后再来问我...(如果你脸皮足够厚的话...)

或许,你仍意犹未尽,或许,你曾经碰到过下面的问题:
在`cmd1 | cmd2 | cmd3 | ...`
这段pipe line中如何将cmd2的输出保存到一个文件呢?

若你写成`cmd1 | cmd2 >file | cmd3`的话,
那你肯定会发现`cmd3`的stdin是空的,(当然了,你都将
水管接到别的水池了)
聪明的你或许会如此解决:
```shell
cmd1 | cmd2 >file; cmd3 < file
```

是的,你可以这样做,但最大的坏处是:
file I/O会变双倍,在command执行的整个过程中,
file I/O是最常见的最大效能杀手。
凡是有经验的shell操作者,都会尽量避免或降低file I/O的频度。

那上面问题还有更好的方法吗?
有的,那就是`tee`命令了。
**所谓的`tee`命令是在不影响原本I/O的情况下,
将stdout赋值到一个文件中去。**
因此,上面的命令行,可以如此执行:
```shell
cmd1 | cmd2 | tee file | cmd3
```
在预设上,`tee`会改写目标文件,
若你要改为追加内容的话,那可用-a参数选项。

基本上,pipe line的应用在shell操作上是非常广泛的。
尤其是在text filtering方面,
如,cat, more, head, tail, wc, expand, tr,
grep, sed, awk...等等文字处理工具。
搭配起pipe line 来使用,你会觉得 command line
原来活得如此精彩的。
常让人有“众里寻他千百度,蓦然回首,那人却在灯火阑珊处”之感...

好了,关于I/O Redirection的介绍就到此告一段落。
若日后,有空的话,在为大家介绍其他在shell上好玩的东西。

##shell十三问之12:你要if还是case呢?
--------------------------------------

还记得我们在第10章所介绍的`return value`吗?

是的,接下来的介绍的内容与之有关,
若你的记忆也被假期所抵消的话,
那建议您还是回去温习温习再回来...

若你记得`return value`,我想你也应该记得了
`&&` 与 `||` 什么意思吧?
用这两个符号再搭配 command group的话,
我们可让shell script变得更加聪明哦。
比方说:
```shell
cmd1 && {
cmd2
cmd3
;
} || {
cmd4
cmd5
}
```
意思是说:
若 cmd1的`return value`为true的话,
然后执行cmd2与cmd3,
否则执行cmd4与cmd5.

事实上, 我们在写shell script的时候,
经常需要用到这样、那样的条件
以作出不同的处理动作。
用`&&`与`||`的确可以达成条件执行的结果,
然而,从“人类语言”上来理解,
却不是那么直观。
更多时候,我们还是喜欢用`if...then...else...`
这样的的keyword来表达条件执行。

在bash shell中,我们可以如此修改上一段代码:
```shell
if cmd1
then
cmd2
cmd3
else
cmd4
cmd5
fi
```

这也是我们在shell script中最常用的`if`判断式:
只要`if`后面的command line返回true的return value
(我们常用`test`命令返回的return value),
然则就执行`then`后面的命令,否则,执行`else`之后的命令,
`fi`则是用来结束判断式的keyword。

在`if`的判断式中,`else`部分可以不用,但`then`是必需的。
(若`then`后不想跑任何command,可用`:`这个`null command`代替)。
当然,then或else后面,也可以再使用更进一层的条件判断式,
这在shell script的设计上很常见。
若有多项条件需要"依序"进行判断的话,
那我们则可使用`elif`这样的keyword:

```shell
if cmd1; then
cmd2;
elif cmd3; then
cmd4
else
cmd5
fi
```
意思是说:
若cmd1为true,然则执行cmd2;
否则在测试cmd3,若为true则执行cmd4;
倘若cmd1与cmd3均不成立,那就执行cmd5。

`if`判断式的例子很常见,你可从很多shell script中
看得到,我这里不再举例子了...

接下来为要为大家介绍的是`case`判断式。
虽然`if`判断式已可应付大部分的条件执行了,
然而,在某些场合中,却不够灵活,
**尤其是在string式样的判断上**,比方如下:

```shell
QQ() {
echo -n "Do you want to continue? (Yes/No): "
read YN
if [ "$YN" = Y -o "$YN" = y -o "$YN" = "Yes" -o "$YN" = "yes" -o "$YN" = YES]
then
QQ
else
exit 0
fi
}

QQ
```

从例中,我们看得出来,
最麻烦的部分是在判断YN的值可能有好几种样式。

聪明的你或许会如此修改:
```shell
QQ() {
echo -n "Do you want to continue? (Yes/No): "
read YN
if echo "$YN" | grep -q '^[Yy]\([Ee][Ss]\)*$'
then
QQ
else
exit 0
fi
}

QQ
```
也就是用`Regular Expression`来简化代码。
(我们有机会,再来介绍`RE`)
只是...是否有其他更方便的方法呢?
有的,就是用`case`判断式即可:
```shell
QQ() {
echo -n "Do you want to continue? (Yes/No): "
read YN
case "$YN" in
[Yy]|[Yy][Ee][Ss])
QQ
;;
*)
exit 0
;;
esac
}

QQ
```
我们常用的`case`的判断式来判断某一变量
在不同的值(通常是string)时,作出不同的处理,
比方说, **判断script参数,以执行不同的命令**。

若你有兴趣,且用linux系统的话,
不妨挖一挖`/etc/init.d/*`中的那堆script中的`case`用法.
如下就是一例:
```shell
case "$1" in
start)
start
;;
stop)
stop
;;
status)
rhstatus
;;
restart|reload)
restart
;;
condrestart)
[ -f /var/lock/subsys/syslog ] && restart || :
;;

*)
echo $"Usage: $0 {start|stop|status|restart|condrestart}"
exit 1
esac
```
(若你对 postional parameter的印象已经模糊了,请重看第9章吧。)

okay,是十三问还剩一问而已,过几天再来搞定之...^_^

##shell十三问之13: for what? while与until差在哪?
------------------------

终于,来到了shell十三问的最后一问了... 长长吐一口气~~~~

最后要介绍的是shell script设计中常见的`循环`(`loop`).
所谓的`loop`就是script中的一段在一定条件下反复执行的代码。

bash shell中常用的`loop`有如下三种:

- for
- while
- until

###1. for loop
--------------

`for` loop 是从一个清单列表中读进变量的值,
并依次的循环执行`do`到`done`之间的命令行。
例:
```shell
for var in one two three four five
do
echo -----------------
echo '$var is '$var
echo
done
```

> 上例的执行结果将会是:

> 1. for会定义一个叫var的变量,其值依次是one two three four five。

> 2. 因为有5个变量值,因此,`do`与`done`之间的命令行会被循环执行5次。

> 3. 每次循环均用`echo`产生3个句子。而第二行中不在hard quote之内的$var会被替换。

> 4. 当最后一个变量值处理完毕,循环结束。

我们不难看出,在`for` loop中,变量值的多寡,决定循环的次数。
然而,变量在循环中是否使用则不一定,得视设计需求而定。
倘若`for` loop没有使用in这个keyword来制变量清单的话,其值将从
`$@`(或`$*`)中继承:
```shell
for var; do
......
done
```
> **Tips:**

> 若你忘记了`positional parameter, 请温习第9章...

`for` loop用于处理“清单”(list)项目非常方便,
其清单除了明确指定或从`postional parameter`取得之外,
也可以从`变量替换`或者`命令替换`取得...
(再一次提醒:别忘了命令行的“重组”特性)
然而,对于一些“累计变化”的项目(整数的加减),for也能处理:
```shell
for ((i = 1; i <= 10; i++))
do
echo "num is $i"
done
```

###2. while loop
---------

除了`for` loop, 上面的例子,
我们也可改用`while` loop来做到:
```shell
num=1
while [ "$num" -le 10 ]; do
echo "num is $num"
num=$(($num + 1))
done
```
`while` loop的原理与`for` loop稍有不同:
它不是逐次处理清单中的变量值,
而是取决于`while` 后面的命令行的return value:

- 若为true, 则执行`do`与`done`之间的命令,
然后重新判断`while`后的return value。
- 若为false,则不再执行`do`与`done`之间的命令而结束循环。

> 分析上例:

> 1. 在`while`之前,定义变量num=1.

> 2. 然后测试(`test`)$num是否小于或等于10.

> 3. 结果为true,于是执行`echo`并将num的值加1.

> 4. 再作第二轮测试,此时num的值为1+1=2,依然小于或等于10,因此,为true,循环继续。

> 5. 直到num为10+1=11时,测试才会失败...于是结束循环。

我们不难发现:
**若`while`的测试结果永远为true的话,那循环将一直永久执行下去**:

```shell
while:; do
echo looping...
done
```
上面的**`:`是bash的null command,不做任何动作,
除了返回true的return value**。
因此这个循环不会结束,称作死循环。

死循环的产生有可能是故意设计的(如跑daemon),
也可能是设计的错误。

若要结束死循环,可通过signal来终止(如按下ctrl-c).
(关于process与signal,等日后有机会再补充,十三问略过。)

####3.until loop
-------

一旦你能够理解`while` loop的话,那就能理解`until` loop:
**与`while`相反, `until`是在return value 为false时进入循环,否则,结束。
因此,前面的例子,我们也可以轻松的用`until`来写:
```shell
num=1
until [ ! "$num" -le 10 ]; do
echo "num is $num"
num=$(($num + 1))
done
```
或者:

```shell
num=1

until [ "$num" -gt 10 ]; do
echo "num is $num"
num=$(($num + 1))
done
```

okay, 关于bash的三个常用的loop暂时介绍到这里。

###4. shell loop中的break与continue
-------------------

在结束本章之前,再跟大家补充两个loop有关的命令:

- `break`
- `continue`
这两个命令常用在复合式循环里,
也就是`do ... done`之间又有更进一层的loop,
当然,用在单一循环中也未尝不可啦... ^_^

`break`用来中断循环,也就是强迫结束循环。
若`break`后面指定一个数值n的话,则从里向外中断第n个循环,
预设值为 `break 1`,也就是中断当前循环。
在使用break时,需要注意的是,它与`return`及`exit`是不同的:

- `break`是结束loop;
- `return`是结束function;
- `exit`是结束script/shell;

而`continue`则与`break`相反:强迫进入下一次循环动作.

若你理解不来的话,那你可简单的看成:
在`continue`在`done`之间的句子略过而返回到循环的顶端...

与`break`相同的是:`continue`后面也可以指定一个数值n,
以决定继续哪一层(从里往外计算)的循环,
预设值为 `continue 1`,也就是继续当前的循环。

在shell script设计中,若能善用loop,
将能大幅度提高script在复杂条件下的处理能力。
请多加练习吧...

---------------------

## shell是十三问的总结语
------------------------

好了,该是到了结束的时候了。
婆婆妈妈地跟大家啰嗦了一堆shell的基础概念。

目的不是要告诉大家“答案”,而是要带给大家“启发”...

在日后的关于shell的讨论中,我或许经常用"连接"的方式
指引十三问中的内容。

以便我们在进行技术探讨时,彼此能有一些讨论的基础,
而不至于各说各话、徒费时力。

但更希望十三问能带给你更多的思考与乐趣,
至为重要的是通过实践来加深理解。

是的,我很重视**实践**与**独立思考**这两项学习要素。

若你能够掌握其中的真谛,那请容我说声:
**恭喜十三问你没白看了** ^_^

p.s.
至于补充问题部分,我暂时不写了。
而是希望:

1. 大家补充题目。
2. 一起来写心得。

Good luck and happy studing!

--------------------------------

##shell十三问原作者**`网中人`**签名中的bash的fork bomb
--------------

最后,Markdown整理者补上本书的原作者**网中人**的个性签名:

> ** 君子博学而日叁省乎己,则知明而行无过矣。**

> 一个能让系统shell崩溃的shell 片段:

```shell
:() { :|:& }; : # <--- 这个别乱跑!好奇会死人的!
echo '十人|日一|十十o' | sed 's/.../&\n/g' # <--- 跟你讲就不听,再跑这个就好了...
```

原来是一个bash的fork炸弹:ref:http://en.wikipedia.org/wiki/Fork_bomb

```shell
:() {

:|:&
}
:
```
> 代码分析:

> (即除最后一行外)

> 定义了一个 shell 函数,函数名是`:`,

> 而这个函数体执行一个后台命令`:|:`

> 即冒号命令(或函数,下文会解释)的输出
通过管道再传给冒号命令做输入

>最后一行执行“:”命令

在各种shell中运行结果分析:
> 这个代码只有在 **bash** 中执行才会出现不断创建进程而耗尽系统资源的严重后果;

> 在 ksh (Korn shell), sh (Bourne shell)中并不会出现,

> 在 ksh88 和传统 unix Bourne shell 中冒号不能做函数名,

> 即便是在 unix-center freebsd 系统中的 sh 和 pdksh(ksh93 手边没有,没试)中冒号可以做函数名,但还是不会出现那个效果。

>原因是 sh、ksh 中内置命令的优先级高于函数,所以执行“:”,
总是执行内置命令“:”而不是刚才定义的那个恐怖函数。

> 但是在 **bash** 中就不一样,bash 中函数的优先级高于内置命令,
> 所以执行“:”结果会导致不断的递归,而其中有管道操作,
> 这就需要创建两个子进程来实现,这样就会不断的创建进程而导致资源耗尽。

众所周知,bash是一款极其强大的shell,提供了强大的交互与编程功能。

这样的一款shell中自然不会缺少“函数”这个元素来帮助程序进行模块化的高效开发与管理。
于是产生了由于其特殊的特性,bash拥有了fork炸弹。

Jaromil在2002年设计了最为精简的一个fork炸弹的实现。

> 所谓fork炸弹是一种恶意程序,它的内部是一个不断在fork进程的无限循环.

> fork炸弹并不需要有特别的权限即可对系统造成破坏。

> fork炸弹实质是一个简单的递归程序。

> 由于程序是递归的,如果没有任何限制,

> 这会导致这个简单的程序迅速耗尽系统里面的所有资源.

##shell十三问之14: [^ ] 跟[! ]差在哪? (wildcard)
--------------------------------------

这个题目说穿了,
就是要探讨Wildcard与Regular Expression的差别的。
这也是很多初学shell的朋友很容易混淆的地方。

首先,让我们回到十三问之第2问,
再一次将我们提到的command line format 温习一次:

```shell
command_name options arguments
```

同时,也再来理解一下,我在第5章所提到的变量替换的特性:
```shell
先替换,再重组 command line!
```

有了这个两个基础后,再让我们来看Wildcard是什么回事吧。

### Part-I Wildcard (通配符)
---------------------------

首先,
```
`Wildcard` 也是属于 `command line` 的处理工序,作用于 `arguments` 里的 `path` 之上。
```

没错,它不用在`command_name`,也不用在`options`上。
而且,若argument不是path的话,那也与wildcard无关。

换句更为精确的定义来讲,
```
`wildcard`是一种命令行的路径扩展(path expansion)功能。
```
提到这个扩展,那就不要忘了 command line的“重组”特性了!

是的,这与`变量替换`(variable subtitution)及
`命令替换`(command substitution)的重组特性是一样的。

也就是在`wildcard`进行扩展后,
命令行会先完成重组,才会交给shell来处理。

了解了`wildcard`的扩展与重组特性后,
接下来,让我们了解一些常见的wildcard吧。

|wildcard | 功能 |
|-----------|--------------------|
| \* | 匹配0个或多个字符|
| ? | 匹配任意单一字符|
| [list] | 匹配list中任意单一字符|
| [!list] | 匹配不在list中任意单一字符|
| {string1,string2,...}| 匹配string1或者stsring2或者(...)中其一字符串|

Note:
list 中可以指定单个字符,如abcd,
也可以指定ASCII字符的起止范围,如 a-d。
即[abcd] 与 [a-d] 是等价的,称为一个自定义的字符类。

例如:
```shell
a*b # a 与 b 之间可以有任意个字符(0个或多个),如aabcb, axyzb, a012b,ab等。
a?b # a 与 b 之间只能有一个字符,但该字符可以任意字符,如 aab, abb, acb, azb等。
a[xyz]b # a 与 b 之间只能有一个字符,但这个字符只能是x或者y或者z,如:axb, ayb, azb这三个。
a[!0-9]b# a 与 b 之间只能有一个字符,但这个字符不能是阿拉伯数字,如aab,ayb,a-b等。
a{abc,xyz,123}b # a 与 b之间只能是abc或者xyz或者123这三个字串之一,扩展后是aabcb,axyzb,a123b。
```

1. `[! ]` 中的`!` 只有放在第一位时,才有取反的功效。
eg:
`[!a]*` 表示当前目录下不以a开头的路径名称;
`/tmp/[a\!]*`表示/tmp目录下所有以a 或者 ! 开头的路径名称;

思考:为何!前面要加\呢?提示是十三问之4.

2. `[ - ]`中`-`左右两边均有字符时,才表示一个范围,否则,仅作`-`(减号)字符来处理。
举例:
`/tmp/*[-z]/[a-zA-Z]*` 表示/tmp 目录下所有以z或者-结尾的子目录中,
以英文字母(不分大小写)开头的目录名称。

3. 以\*或?开头的wildcard不能匹配隐藏文件(即以.开头的文件名)。
eg:
`*.txt`并不能匹配`.txt`但能匹配1.txt这样的路径名。
但1*txt及1?txt均可匹配1.txt这样的路径名。

基本上,要掌握wildcard并不难,
只要多加练习,再勤于思考,就能灵活运用了。

再次提醒:
```
别忘了wildcard的"扩展" + "重组" 这个重要特性,而且只作用在 argument的path上。
```
比方说,
假如当前目录下有:
a.txt b.txt c.txt 1.txt 2.txt 3.txt 这几个文件。

当我们在命令行中执行`ls -l [0-9].txt`的命令行时,
因为wildcard处于argument的位置上,

于是根据匹配的路径,扩展为: 1.txt 2.txt 3.txt,
在重组出`ls -l 1.txt 2.txt 3.txt` 这样的命令行。

因此,你在命令行上敲 `ls -l [0-9].txt`
与 `ls -l 1.txt 2.txt 3.txt` 输出的结果是一样,
原因就是在于此。

##shell十三问之15: [^ ] 跟[! ]差在哪? (RE: Regular Expression)
------------------------------------------------------------

### Part-II Regular Expression (正则表达式)
------------------------------------------

接下来的Regular Expression(RE) 可是个大题目,要讲的很多。
我这里当然不可能讲得很全。
只希望能带给大家一个基本的入门概念,就很足够了...

先来考一下英文好了:What is expression?
简单来说,就是"表达",也就是人们在沟通的时候所要陈述的内容。

然而,生活中,表达方要清楚的将意思描述清楚,
而让接收方完整无误地领会,可不是件容易的事情。

因而才会出现那么多的"误会", 真可叹句"表达不易"啊......

同样的情形也发生在计算机的数据处理过程中,
尤其是当我们在描述一段"文字内容"的时候....
那么,我们不禁要问:
有何方法可以让大家的误会降至最低程度,
而让表达的精确度达到最高程度呢?
答案就是"标准化"了,
也就是我们这里要谈的`Regular Expression`啦...^_^

然而,在进入`RE`介绍之前,不妨先让我们温习一下shell十三问之第4问,
那就是关于quoting的部分。

**关键是要能够区分 shell command line上的meta与literal的这两种不同的字符类型**。

然后,我这里也跟你讲:
**RE 表达式里字符也分meta与literal这两种**。

呵,不知亲爱的读者是否被我搞混乱了呢?... ^_^

这也难怪啦,因为这的确是最容易混淆的地方,
刚学`RE`的朋友很多时候,都死在这里!
因此,请特别小心理解哦...

简单而言,除非你将`RE`写在特定程序使用的脚本里,
否则,我们的`RE`也是通过 command line输入的。
然而,
**不少RE所使用的meta字符,跟shell 的meta字符是冲突的**。

比方说,
**`*`这个字符,在RE里是一个modifier(修饰符);而在command line上,确是wildcard(通配符)**。

那么,我们该如何解决这样的冲突呢?
关键就是看你对shell十三问的第4问中所提的quoting是否足够理解了!

若你明白到
**shell quoting 就是用来在command line上关闭shell meta这一基本原理**,
那你就能很轻松的解决 RE meta与shell meta的冲突问题了:
**用shell quoting 关闭掉shell meta就是了**。
就这么简单... ^_^

再以刚提到`*`字符为例,
若在command line的path中没有quoting处理的话,
如abc\* 就会被作为wildcard expression来扩充及重组了。
若将其置于quoting中,即"abc\*",则可以避免wildcard expand的处理。

好了,说了大半天,还没有进入正式的RE介绍呢....
大家别急,因为我的教学风格就是要先建立基础,循序渐进的... ^_^
因此, 我这里还要再啰嗦一个观念,才会到RE的说明啦...(哈...别打我...)

当我们在谈到RE时,千万别跟wildcard搞混在一起!
尤其是
```
在command line的位置里,wildcard只作用于argument的path上;
而RE却只用于"字符串处理" 的程序中,这与路径名一点关系也没有。
```
> **Tips:**
> RE 所处理的字符串,通常是指纯文本或通过stdin读进的内容。

okay,够了够了,我已看到一堆人开始出现不耐烦的样子了... ^_^
现在,就让我们登堂入室,揭开RE的神秘面纱吧,
这样可以放过我了吧? 哈哈...

在RE的表达式里,主要分为两种字符:`literal`与`meta`。
所谓`literal`就是在RE里不具有特殊功能的字符,如abc,123等;
而`meta`,在RE里具有特殊的功能。
要关闭之,需要在`meta`之前使用escape(\)转义字符。

然而,在介绍`meta`之前,先让我们来认识一下字符组合(character set)会更好些。

一、所谓的char set就是将多个连续的字符作为一个集合。
例如:

| char set | 意义
|-------------|----------------------------
| abc | 表示abc三个连续的字符,但彼此独立而非集合。(可简单视为三个char set)|
| (abc) | 表示abc这三个连续字符的集合。(可简单视为一个char set)|
| abc\|xyz | 表示abc或xyz这连个char set之一|
|[abc] | 表示单一字符,可为a或b或c;与wildcard的[abc]原理相同,称之为字符类。|
|[^abc] |表示单一字符,不为a或b或c即可。(与wildcard [!abc]原理相同)|
| . | 表示任意单个字符,(与wildcard的?原理相同)|

note: abc|xyz 表示abc或xyz这连个char set之一

在认识了RE的char set这个概念之后,然后,在让我们多认识几个RE中常见的meta字符:

二、 锚点(anchor): 用以标识RE在句子中的位置所在。
常见的有:

|锚点 | 说明|
|-------|-------|
| ^ | 表示句首。如,^abc表示以abc开头的句子。|
| $ | 表示句尾。如,abc$表示以abc结尾的句子。|
| \< | 表示词首。如,\ | 表示词尾。如,abc\>表示以abc结尾的词。|

三、 修饰符(modifier):独立表示时本身不具意义,专门用以修饰前一个char set出现的次数。
常见的有:

| modifier | 说明|
|------------|-----------------------------------
| * | 表示前一个char set出现0次或多次,即任意次。如ab*c表示a与c之间可以有0个或多个b。|
| ? | 表示前一个char set出现0次或1次,即至多出现1次。如ab?c 表示a与c之间可以有0个或1个b。|
| + | 表示前一个char set出现1次或多次,即至少出现1次。如ab+c 表示a与c之间可以有1个或多个b。|
| {n} | 表示前一个char set出现n次。如ab{n}c 表示a与c之间可以有n个b。|
| {n, } | 表示前一个char set至少出现n次。如ab{n}c 表示a与c之间至少有n个b。|
| {n, m} | 表示前一个char set至少出现n次,至多出现m次。如ab{n,m}c 表示a与c之间至少有n个b,至多有m个b。|


然而,当我们在识别modifier时,却很容易忽略"边界(boundary)字符"的重要性。

以`ab{3,5}c`为例,这里的a与c就是边界字符了。
若没有边界字符的帮忙,我们很容易做出错误的解读。
比方说: 我们用`ab{3,5}`这个RE(少了c这个边界字符)
可以抓到"abbbbbbbbbb"(a后面有10个b)的字符串吗?
从刚才的modifier的说明,我们一般认为,我们要的b是3到5个,
若超出了此范围,就不是我们所要表达的。
因此,我们或许会很轻率地认为这个RE抓不到结果(上述"abbbbbbbbbb"字符串)。

然而,答案却是可以的!为什么呢?
让我们重新解读`ab{3,5}`这个RE看看:
我们要表达的是a后接3到5个b即可,但3到5个b后面,我们却没有规定什么,
因此,在RE后面可以是任意的字符串,当然包括b也可以啦!(明白了吗?)

同样,我们用`b{3,5}c`也同样可以抓到"abbbbbbbbbbc"
这样的字符串。

但当我们用`ab{3,5}c`这样的RE时,
由于同时有a与c这连个边界字符,就截然不同了!

有空在思考一下,为何我们用下面这些RE都抓到abc这样的字符串呢?
```
x*
ax*, abx*, ax*b
abcx*, abx*c, ax*bc
bx*c, bcx*, x*bc
```
但, 若我们在这些RE前后分别加`^`与`$`这样的anchor,那又如何呢?

刚学RE时,只要能掌握上面这些基本的meta的大概就可以入门了。
一如前述,RE是一种规范化的文字表达式,
主要用于某些文字处理工具之间,如:
grep, perl, vi,awk,sed,等等,
常用于表示一段连续的字符串,查找和替换。

然而每种工具对RE表达式的具体解读或有一些细微差别,
不过节本原理还是一致的。
只要掌握RE的基本原理,那就一理通百理了,
只是在实践时,稍加变通即可。

比方以grep来说,
在Linux上,你可以找到grep,egrep,fgrep这些程序,
其差异大致如下:

grep: 传统的grep程序,在没有任何选项(options)的情况下,只输出符合RE字串的句子,
其常见的选项如下:

| 选项 (option)| 用途|
| -----|----------------------------
| -v | 反模式, 只输出“不含”RE的字符串的行。|
| -r | 递归模式,可同时处理所有层级的子目录里的文件|
| -q | 静默模式,不输出任何结果(stderr 除外,常用于获取return value,符合为true,否则,为false.|
| -i | 忽略大小写|
| -w | 整词匹配,类似 \|
| -n | 同时输出行号|
| -l | 输出匹配RE的文件名|
| -o | 只输出匹配RE的字符串。(gna新版独有,不见得所有版本支持)|
| -E | 切换为egrep|

egrep:为grep的扩充版本,改良了许多传统grep不能或者不便的操作,

- grep下不支持`?`与`+`这两种meta,但egrep支持;
- grep 不支持`a|b`或(`abc|xyz`)这类“或一”的匹配,但egrep支持;
- grep 在处理`{n,m}`时,需要\\{ 与 \\}处理,但egrep不需。

等诸如此类的。我个人建议能用egrep就不用grep啦...^_^

fgrep: 不作RE处理,表达式仅作一般的字符串处理,所有的meta均市区功能。

好了,关于RE的入门,我们暂时就介绍到这里。
虽然有点乱,且有些观念也不恨精确,
不过,姑且算是对大家的一个交差吧...^_^
若这两天有时间的话,我在举些范例来分析一下,以帮助大家更好的理解。
假如更有可能的话,也顺道为大家介绍一下sed这个工具。

---------------------------------------

### Part-III eval
---------------------------------------

讲到command line的重组特性,
真的需要我们好好的加以解释的。

如此便能抽丝剥茧的一层层的将整个command line分析的
一清二楚,而不至于含糊。

假如这个重组的特性理解了,那我们介绍一个好玩的命令:`eval`.

我们在变量替换的过程中,常会碰到所谓的复式变量的问题:
如:
```shell
a=1
A1=abc
```
我们都知道`echo $A1`就可以得到abc的结果。
然而,我们能否用$A$a来取代$A1,而同一样替换为abc呢?

这个问题我们可用很轻松的用`eval`来解决:
```shell
eval echo \$A$a
```
说穿了,`eval` 只不过是在命令行完成替换重组后,
在来一次替换重组罢了...
就是这么简单啦~~~ ^_^

##shell十三问之16:学习总结与原帖目录
---------------------------------

本人(markdown译者)是解决工作中shell脚本的一个问题,
偶尔的一次机会遇到了CU论坛中这样一个神贴:**shell十三问**.

shell十三问是CU的shell版的台湾的**网中人**是2003年用繁体发布的。
第一次读到shell十三问,由于是繁体,第一感觉有点抵触,
但是还是耐着性子读完了一贴,没想到竟然读懂了,
而且还被**网中人**的幽默的写作风格,独到的思维方式,
循序渐进的认识事物的过程所折服。

尽管帖子是10多年前写的,今天看来也几乎没有一点过时的感觉。
从这个方面来说,shell十三问应该shell的(思想)精华本质所在,
就像武功的内功心法,可能我说的点过,
但是我曾经看过一本shell脚本学习指南,看完后的感觉,还是有感念很朦胧,
而shell十三问是我最容易理解和接受的,这也是我整理的Markdown版本初衷。
为什么不让好东西让更多的人熟知呢,恰好年前项目管理开始迁移到git上,
在git上认识一个好东西Markdown,用它可以很简单地整理出条例清晰篇章。
在年假的时候,觉得这个假期该做点什么,
毕竟马总都说了,改变世界,不如改变自己。

本人整理的 [简体中文Markdown版本的shell十三问][shell-markdown] 的链接地址: https://github.com/wzb56/13_questions_of_shell

网中人的CU原帖[shell十三问][0]地址:http://bbs.chinaunix.net/thread-218853-1-1.html

我简单将原文整理如下:

我在CU的日子并不长,有幸在shell版上与大家结缘。
除了跟前辈学习到不少技巧之外,也常看到不少朋友的问题。
然而,在众多问题中,我发现许多瓶颈都源于shell的基础而已。
每次要解说,却总有千言万语不知从何而起之感......

这次,我不是来回答,而是准备了关于shell基础的十三个问题要问大家。
希望的shell的学习者们能够通过寻找答案的过程,好好的将shell基础打扎实一点。

当然了,这些问题我也会逐一解说一遍。
只是,我不敢保证什么时候能够完成这趟任务。

除了时间关系外,个人功力实在有限,很怕匆忙间误导观众就糟糕了。
若能抛砖引玉,诱得,其他前辈出马补充,那才是功德一件。

###shell十三问:

1. [为何叫做 shell?][1]

2. [shell prompt(PS1) 与 Carriage Return(CR) 的关系?][2] (2008-10-30 02:05 最后更新)

3. [別人 echo、你也 echo ,是问 echo 知多少?][3]( 2008-10-30 02:08 最后更新)

4. [" "(双引号) 与 ' '(单引号)差在哪?][4] (2008-10-30 02:07 最后更新)

5. [var=value 在export前后差在哪?][5] (2008-10-30 02:12 最后更新)

6. [exec 跟 source 差在哪?][6] (2008-10-30 02:17 最后更新)

7. [( ) 与 { } 差在哪?][7]

8. [$(( )) 与 $( ) 还有${ } 差在哪?][8] (2008-10-30 02:20 最后更新)

9. [$@ 与 $* 差在哪?][9]

10. [&& 与 || 差在哪?][10] (2008-10-30 02:21 最后更新)

11. [> 与 < 差在哪?][11] (2008-10-30 02:24 最后更新)

12. [你要 if 还是 case 呢?] [12] (2008-10-30 02:25最后更新)

13. [for what? while 与 until 差在哪?][13] (2008-10-30 02:26最后更新)

14. [[^ ] 跟 [! ] 差在哪?][14]

15. [Part-I: Wildcard][14] (2008-10-30 02:25 最後更新)
16. [Part-II Regular Expression][15] (2008-10-30 02:26 最后更新)

--------------
说明:

1. 欢迎大家补充/扩充问题。

2. 我接触电脑的中文名称时是在台湾,因此一些术语或与大陆不同,请自行转换。

3. 我会不定时"逐题"说明(以 Linux 上的 bash 为环境)
同时,也会在任何时候进行无预警的修改。请读者自行留意。

4. 本人于本系列所发表的任文章均可自由以电子格式(非印刷)引用、修改、转载,
且不必注明出处(若能注明 CU 更佳)。当然,若有错漏或不当结果,本人也不负任何责任。

5. 若有人愿意整理成册且付印者,本人仅保留著作权,版权收益之 30% 須捐赠于 CU 论坛管理者,剩余不究。

---------
建议參考谈论:

1. shaoping0330 兄关于变量替换的补充:(链接在改版后已经失效)

2. shaoping0330 兄[关于 RE][16] 的说明:

3. 关于 nested subshell 的讨论:(链接在改版后已经失效)

4. [关于 IFS 的讨论][18]:

---------

* 感谢 lkydeer 兄整理 [word/pdf 版本][19]方便大家参考:

[shell-mardown]: https://github.com/wzb56/13_questions_of_shell
[0]: http://bbs.chinaunix.net/thread-218853-1-1.html
[1]: http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=218853&page=2#pid1454336
[2]: http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=218853&page=2#pid1467910
[3]: http://bbs.chinaunix.net/viewthread.php?tid=218853&extra=&page=3#pid1482452
[4]: http://bbs.chinaunix.net/viewthread.php?tid=218853&extra=&page=4#pid1511745
[5]: http://bbs.chinaunix.net/viewthread.php?tid=218853&extra=&page=5#pid1544391
[6]: http://bbs.chinaunix.net/viewthread.php?tid=218853&extra=&page=6#pid1583329
[7]: http://bbs.chinaunix.net/viewthread.php?tid=218853&extra=&page=6#pid1595135
[8]: http://bbs.chinaunix.net/viewthread.php?tid=218853&extra=&page=7#pid1617953
[9]: http://bbs.chinaunix.net/viewthread.php?tid=218853&extra=&page=7#pid1628522
[10]: http://bbs.chinaunix.net/viewthread.php?tid=218853&extra=&page=7#pid1634118
[11]: http://bbs.chinaunix.net/viewthread.php?tid=218853&extra=&page=7#pid1636825
[12]: http://bbs.chinaunix.net/viewthread.php?tid=218853&extra=&page=8#pid1679488
[13]: http://bbs.chinaunix.net/viewthread.php?tid=218853&extra=&page=8#pid1692457
[14]: http://bbs.chinaunix.net/viewthread.php?tid=218853&extra=&page=16#pid2930144
[15]: http://bbs.chinaunix.net/viewthread.php?tid=218853&extra=&page=16#pid2934852
[16]: http://bbs.chinaunix.net/forum/viewtopic.php?t=393964
[18]: http://bbs.chinaunix.net/forum/viewtopic.php?t=512925
[19]: http://bbs.chinaunix.net/viewthread.php?tid=963890&extra=page%3D2