1.  使用外壳语言


某天,我的父亲修理它浮船上的电线,他忙了几个小时也没有成功,尽管他累得要死,也没能把照明灯搞定,灰心丧气之后,他把灯关上了…然而灯却亮了。

本章简短的概述一下在BASH命令提示符的交互会话中如何使用外壳编程。为了避免向我父亲的电线问题一样,掉入脚本语言的复杂性中,先学习一下命令是如何工作的,这在交互级别中是很重要的,除非你想遇到意料之外的惊奇。

BASH使用的关键字


关键字是一个单词或符号,它在计算机语言中有特定的含义。下面的单词或符号对于BASH都有特定的含义:

!           esac        select             }

Case              fi            then        [[

Do          for          until        ]]

Done       function  while

Elif         if            time

Else        in            {

和其他计算机编程语言不同,Bash允许关键字用作变量名,这可能导致语法难以理解。为了容易理解脚本,请尽量不要把关键字用作变量名。

命令基础


在Bash外壳提示符下显示的命令通常是保存在外部文件系统内的Linux程序。还有一些命令因为速度、标准化或只适合运行在内置外壳中原因,由外壳程序本身提供。

无论命令来自哪里,它通常根据信息类别进行分类。

工具(Utilities)是在许多应用中都使用的命令,例如日期命令(date)、统计文件内容行数的命令。

过滤器(Filters)是将一个命令的结果进行某种方式的修改(例如去除不想要的行或替换某个单词等)。许多命令在合适的环境下都可以用作过滤器。

为了执行某个命令,在Bash命令提示符后输入该命令。命令提示符通常是“$”符号。但是在Linux环境中可以进行自定义。例如在.S.u.S.E.中提示符为“a>”。

命令date在屏幕上显示当前的日期和时间。

$ date

Wed Apr 4 10:44:52 EDT 2001

所有的命令包括外壳命令都是大小写敏感的。为了使用方便,通常外壳命令都是小写。

$ DATA

Bash: DATE: command not found

参数是提供给命令的额外信息以使命令来改变它的运行结果。命令date有一个格式参数可以使时间和日期的显示格式进行设定。

$ date ‘+%H:%M’

10:44

开关(Switch也成为选项或标志)是一个字符加一个前导符号“-”,可以增加命令的功能。例如:date显示UTC(全球统一时间)时间,可以加上“-u”开关。

$date –u

Wed Apr 4 14:46:41 UTC 2001

因为“选项”和“标准”用于许多场景之中,因此在本书中我们称之为“开关”。

开关和参数(argument)我们统称为参数(paramenter)。一些命令允许有许多参数或参数有非常复杂的形式。

GNU和Linux实际上允许参数以长字符和更容易理解的方式进行处理,但是前面要加一个“-”符号,例如“-u”等于“--universal”

$date –universal

Wed Apr 4 14:46:41 UTC 2001

长开关能够提醒读者准确的理解参数的含义,这使以后的调试更加容易,因为Linux中充斥着短开关。大部分Linux命令都可以识别长开关“--help”、“--verbose”、“--version”。

注释可以加在命令的结尾,组成一行。注释使用“#”符号表示。

$date --universal # show the date in UTC format.

内置的Bash命令和大部分其他GNU软件经“--”符号看做特殊格式的开关,表明开关列表的结束,这样就可以只有一个“-”符号的参数了。

命令行编辑


有一些特殊的键组合可以帮助你输入或复制先前的命令。

Bash有两种编辑模式,这两种是Linux环境中比较流行的两种编辑器采用的快捷键。Vi模式(模拟vi和vim编辑器)和Emacs模式(模拟emacs、nano和pico编辑器)。

当前的编辑模式可以使用命令shopt进行指定或切换。命令shopt –o emacs打开emacs编辑模式。命令shopt –o vi打开vi编辑模式。在某一时刻只能有一种编辑模式。

$ shopt –o emacs

Emacs            on

$shopt –o vi

Vi                  off

无论那种模式,都使用方向键在最近用的命令中进行选择命令和已输入的字符:

n         左箭头——移动到字符的左边,不擦处已输入的字符。

n         右箭头——移动到字符的右边。

n         上箭头——在命令历史表中移到上一条命令。

n         下箭头——在命令历史表中移动到下一条命令。

使用左右箭头,光标可以移动到命令的任何位置。在一行的中间,新的文本可以添加到此行的中间,而不用替换掉原先的文本。

Emacs模式是大部分已发行的Linux版本的缺省模式。大部分emacs快捷键如下所示:

n         Ctrl-b——移动到字符的左边,不删除已输入的字符。

n         Ctrl-f——移动到字符的右边。

n         Ctrl-p——在历史命令表中移到前一条命令。

n         Ctrl-n——在历史命令表中移动到下一条命令。

n         Tab键——如果只有一条和已输入文本相匹配的文件名,找到它并完成。

文件名完成功能会试图找到和你输入的部分文本相匹配的完整文件名并完成你的输入。例如:

$dat

按下Tab键后

$date

如果date是唯一一条和dat相匹配的文件就会显示上面的命令。

vi模式的组合快捷键如下所示:

n         Esc——进入/退出编辑模式。

n         h——移动光标到字符的左边,不删除已输入的字符。

n         l——移动光标到字符右边。

n         k——在历史命令表中选择前一条已输入的命令。

n         j——在历史命令表中选择后一天已输入的命令。

n         Esc两次——命令自动完成功能。

一份完整的组合键列表存放在Bash的手册页中的Readline段。缺省的组合键可以使用bind命令进行更改、显示和重新分配。为了避免混淆,你最好使用缺省值,除非已有一套自己的应用程序要设置。

还有一些编辑键使用旧的Linux stty命令。运行stty命令显示常用的命令快捷键和相关会话的信息。使用“-a”开关设置所有选项。

$stty

speed 9600 baud; evenp hupcl

intr =  ^C; erase = ^?; kill = ^X;

eol2 = ^@; swtch = ^@;

susp = ^Z; dsusp = ^Y;

werase = ^W; lnext = ^@;

-inpck –istrip icrnl –ixany ixoff onlcr

-iexten echo echoe echok

-echoctl -echoke

其中许多设置只用在串口设备工作的时候,否则直接忽略它们即可。另一些设置是控制组合键,使用“^”符号标记。快捷键“^@”没有定义,这些键如下所示:

n         erase——向左移动并删除一个字符(^?)。

n         intr——中断/停止当前程序或取消当前行(^C)。

n         kill——删除当前行(^X)。

n         rprnt——重画当前行(^R)。

n         stop——暂停程序以便你可以读取屏幕结果(^S)。

n         start——继续程序(^Q)。

n         susp——挂起当前程序(^Z)。

n         werase——删除上一个输入的单词(^W)。

为了挂起字符到control-v,输入:

$stty susp ‘^v”

要改变组合键是非常困难的,例如:如果你在微软的windows系统下运行一个X Windows Server来访问Linux计算机,组合键会被下列系统影响。

n         Microsoft Windows

n         The X server software

n         The Linux window manager

n         The stty settings

它们就像洋葱皮一样,一层套一层,如果想要某一种功能,必须协调一致。例如:shift-insert通常用于粘贴文本,也许在Linux处理前要先被X Window处理或者它会被外壳程序先处理。

变量分配和显示消息


变量可以建立或使用等号进行分配文本,文本必须使用双引号罩起来。

$FILENAME=”info.txt”

变量的值可以使用printf命令打印出来。printf命令有两个参数:格式化代码和要显示的变量。格式化代码是”%s/n”,变量名跟在后面,变量名使用“$”符号做前缀,并使用双引号罩起来。

$ printf  ”%s/n” ”$FILENAME”

info.txt

printf也能显示简单的文本信息,只需将要显示的消息放置在格式化代码的位置即可。

$ printf “Bash is a great shell./n”

Bash is a great shell.

printf和变量在外壳脚本程序中有极其重要的角色,在后面的章节中会加以详细的描述。

也可以使用后引号把命令的结果分配给一个变量。

$DATE = `date`

$printf  “%s/n” “$DATE”

Wed Feb 13 15:36:41 EST 2002

当DATE被分配了date命令的值后,显示日期。变量的值分配后,不会更改,直到从新分配变量的值后才会更改。

$DATE = `date`

$printf  “%s/n” “$DATE”

Wed Feb 13 15:36:41 EST 2002

$DATE = `date`

$ printf “%S/n”

Wed Feb 13 15:36:48 EST 2002

多个命令


一行可以输入多个命令,如何执行这些命令取决于使用什么符号进行分割。

如果使用分号进行分割,命令会一个跟一个的执行。

$ printf  “%s/n” “This is executed” ; printf “%s/n” “And so is this”

This is executed

And so is this

如果使用“&&”符号进行连接,执行命令一直到有命令执行失败了,否则执行所有的命令。

$ date && printf “%s/n” “The date command was successful”

Wed Aug 15 14:36:32 EDT 2001

The date command was successful

如果命令由“||”符号分割,那么前面所有的命令都失败了才把命令执行完。

$ date ‘duck!’ || printf “%s/n” “The date command failed”

date: bad conversion

The date command failed

一行命令中可以自由组合分号、双与号和双竖号。

$ date ‘format-this!’ || printf “%s/n” “The date command failed” && printf “%s/n” “But /

the printf didn’t!”

date:bad conversion

The date command failed

But the printf didn’t!

编写脚本时有一个惯例:因为一行命令中使用了重定向符例如“>”,该行命令就很难理解,应该避免这种事情。

历史命令表


Bash保存了一张最近使用的命令历史表,该表称之为命令历史表。

浏览命令历史表最容易的方式是使用上下箭头。此历史表可以使用“!”进行查找。这表示命令的开始到完成由Bash来完成。Bash执行最近使用的命令进行匹配。例如:

$date

Wed Apr 4 11:55:58 EDT 2001

$ !d

Wed Apr 4 11:55:58 EDT 2001

如果找不到匹配的命令,Bash会回复一条事件查找错误的消息。

$ !x

bash: !x: event not found

使用“!!”符号执行最上面一条命令。

$date

Thu Jul 5 14:03:25 EDT 2001

$!!

Thu Jul 5 14:03:28 EDT 2001

可以使用“!”符号加数字的方式来选择特定的命令。

数字前面加一个“-”符号来表示相对的行号。即:在命令历史表中查找要执行的命令号。“!!”和“!-1”相同。

$date

Thu Jul 5 14:04:54 EDT 2001

$printf “%s/n” $PWD

/home/kburtch/

$!-2

date

Thu Jul 5 14:05:15 EDT 2001

符号“!#”重复当前行的命令(不要和“#!”符号混淆了)。使用这个命令表示执行当前行两次。

$date ; sleep 5 ; !#

date ; sleep 5 ; date ; sleep 5 ;

Fri Jan 18 15:26:54 EST 2002

Fri Jan 18 15:26:59 EST 2002

Bash将命令历史表保存在文件.bash_history中,如果要改变文件名请设置变量HISTFILE。每次退出Bash会话,Bash保存本次会话到历史文件表中。如果打开了histappend选项,历史命令的行数扩充到历史文件允许的最大值。每次开始Bash会话时,都会从历史文件表中加载历史命令。

还有一个外壳选项histverify,使你可以在获取命令之后可以编辑它,而不是立即执行它。

Bash有一个内置的命令history,它可以对命令历史给予完全的控制。执行不带参数的history命令列出命令历史。如果你不想看到所有的命令历史,可以定义要显示的命令行数。

$ history 10

1026 set –o emacs

1027 stty

1028 man stty

1029 stty -a

1030 date edhhh

1031 date edhhh

1032 date

1033 date

1034 !

1035 history 10

你可以使用“-p”开关来测试哪个命令匹配命令历史表的命令自动完成功能。

$ history –p !d

history –p date

date

你可以根据行号来指定特定的命令行。

$ !1133

date

Thu Jul 5 14:09:05 EDT 2001

history –d可以删除某个历史条目。

$ history –d 1029

$ history 10

1027 stty

1028 man stty

1029 date edhhh

1030 date edhhh

1031 date

1032 date

1033 !

1034 history 10

1035 history –d 1029

“-s”开关增加新的历史命令条目。“-w(写)”和“-r(读)”开关从某个文件中保存和加载命令历史。“-a(添加)”开关添加当前的会话历史到历史文件中。当你退出外壳文件后,这会自动完成。“-n”开关从命令历史文件中加载完整的命令历史。history –c(清除)将删除所有的命令历史。

命令历史表可以使用“!?”进行搜索。如果搜索字符在命令的中间,搜索字符尾部还要加一个“?”。

$date

Thu Jul 5 14:12:33 EDT 2001

$!?ate

date

Thu Jul 5 14:12:38 EDT 2001

$ !?da? ‘+%Y’

date ’+%Y’

2001

快速历史替换命令“^“,可以再次运行上一次输入的命令并替换其中的某一字符串。

$date ’+%Y’

2001

$ ^%Y^%m^

date ’%m’

07

Bash历史命令表可以使用“-o”开关来关闭。cmdhist选项可以在命令历史表中保存多行命令。lithist选项可以把分号划分的命令分成每行一个命令。

目录命令


内置命令pwd返回当前的工作目录名。

$pwd

/home/kburtch

虽然你可能认为这个简单的目录不需要选项,但是pwd有两个选项。“-P”开关显示实际的目录,缺省使用“-L”开关显示目录并包含任何符号连接。例如:假如/home是一个对/user_drive/home的链接目录,这两个开关的不同如下所示:

$pwd –P

/user_drive/home/kburtch

$pwd –L

/home/kburtch

内置的cd命令更改当前的目录。前面第一章我们讨论的特殊目录“..”表示父目录,“.”表示当前目录。

$pwd

/home/kburtch

$cd .

$pwd

/home/kburtch

$cd..

pwd

/home

$cd kburtch

$pwd

/home/kburtch

每次你更改当前目录,Bash将会更新PWD变量所包含的当前工作目录。Bash也包含了OLD-PWD的变量保存更改前的目录。

使用“cd -”,你能在当前目录和上一次的目录中进行切换。如果你在两个不同的目录中工作时,这是非常有用的。

$pwd

/home/kburtch

$cd ..

$pwd

/home

$cd –

$pwd

/home/kburtch

$cd –

$pwd

/home

符号“~”表示当前的目录,使用它可以做主目录相对路径的转移。如果要移动到主目录内的mail目录内,可以输入:

$cd ~/mail

“.”和“..”可以使用在所有的Linux程序中,“~”和“-”是Bash的特性,只可以使用在Bash和Bash脚本中。

cd本身返回到你的主目录和cd ~是一样的。

如果CDPATH变量存在,它包含了一张和PATH变量类似的列表。这是你回到了只能使用终端的外壳会话的时代,现在使用它是因为安全风险的原因。应该避免使用。

特殊的目录浏览和目录历史表


因为大部分用户可以打开多个外壳会话,所以很少需要在目录之间进行复杂的移动。“cd –”可以在两个目录之间进行切换,它可以适合大部分情况。然而,你可能被限制在一个外壳会话中并想让Bash记住多个目录,有三个内置的命令可以维护一张目录列表。

内置命令dirs显示已保存的目录列表。当前目录总是列表的第一项。

$dirs

~

内置命令pushd增加一个目录到列表中并更改当前目录到新的目录中。

$pushd /home/kburtch/invoices

~/invoices ~

$pushd /home/kburtch/work

~/work ~/invoices ~

$pwd

/home/kburtch/work

现在目录列表中有三个目录。

“-n”开关将放置一个目录到列表中而不用更改当前目录。“-N”开关移动列表中的命令从左边到第N个位置(“+N”从右边移动)。

“dirs –l”开关显示目录的全名。

$dirs –l

/home/kburtch/work /home/kburtch/invoices /home/kburtch

“-v”开关以列的形式显示目录列表,“-p”显示同样的信息而不是按照列表的位置。“-c”清除列表。“-N”从左边显示第N个目录(“+N”从右边显示第N个目录)。

$dirs +1

~

内置命令popd的功能和pushd相反。popd去除列表中第一个目录并移动到列表的第二个目录中。

$popd

~/invoices ~

$pwd

/home/kburtch/invoices

popd命令的开关类似于pushd:“-n”弹出目录而不移动目录,“-N”从左边数第N个目录将会被删除(“+N”从右边开始)。

冒号命令


最简单的外壳命令是冒号——“:”。这个命令有时称之为空命令,它不做任何事情。如果这个命令不做任何事情还要这个命令干什么呢?有时在外壳程序中它是特别有用的,这时使用“:”命令来表示无需做任何事。

在提示符后输入:没有任何效果。

$:

$

冒号命令可以带有参数使文件重定向。这样会有一个特别的效果,例如后面跟上date命令,并加上后引号。结果是date命令不显示任何结果。

$: `date`

$

这和从定向输出到/dev/null文件的命令有同样的效果。

$date >/dev/null

后引号和从定向符号会在下一章进行讨论。

参考区


date命令开关


n         --date = s(或-d s)——显示由s描述的时间

n         --file = f(或-f f)——显示由文件f列出的时间

n         --iso-8601 = t(或-I t)——显示ISO-8601标准时间

n         --reference = f(或-r f)——显示文件上一次修改时间

n         --rfc-833(或-r)——使用RFC-822格式

n         --universal(或-utc或-u)——使用统一全球时间

stty命令开关


n         -all(或-a)显示所有的stty设置。

n         --save(或-g)显示设置以便它们用作stty的参数。

n         --file = d(或-F d)打开设备d而不是stdin。

history命令开关


n         -a——添加到历史命令表文件中

n         -c——清除命令历史

n         -d——删除历史条目

n         -n——从历史文件中加载

n         -p——执行历史命令的查找或替换

n         -r——从文件读书数据到命令历史表中

n         -s——增加新的历史条目

pwd命令开关


-P——物理目录

-L——逻辑目录

dirs命令开关


n         -c——清除所有的条目

n         -l——水平显示列表

n         -p——列出所有的条目

n         -v——垂直显示列表