Linux命令行-第11章@shell脚本编程-构建基本脚本①重定向输入和输出
第 11章 构建基本脚本
本章内容
使用多个命令
创建脚本文件
显示消息
使用变量
输入输出重定向
管道
数学运算
退出脚本
现shell脚本的基础知识。在开始编写自己的shell脚本大作前,你必须了解这些基本概念。
在我们已经知道了Linux系统和命令行的基础知识,是时候开始编程了。本章讨论编写
11.1 使用多个命令
到目前为止,你已经了解了如何使用shell的命令行界面提示符来输入命令和查看命令的结果。shell脚本的关键在于输入多个命令并处理每个命令的结果,甚至需要将一个命令的结果传给另一个命令。shell可以让你将多个命令串起来,一次执行完成。如果要两个命令一起运行,可以把它们放在同一行中,彼此间用分号隔开。
$ date ; who
Mon Feb 21 15:36:09 EST 2014
Christine tty2 2014-02-21 15:26
Samantha tty3 2014-02-21 15:26
Timothy tty1 2014-02-21 15:26
user tty7 2014-02-19 14:03 (:0)
user pts/0 2014-02-21 15:21 (:0.0)
$
恭喜,你刚刚已经写好了一个脚本。这个简单的脚本只用到了两个bash shell命令。date命令先运行,显示了当前日期和时间,后面紧跟着who命令的输出,显示当前是谁登录到了系统上。使用这种办法就能将任意多个命令串连在一起使用了,只要不超过最大命令行字符数255就行。
这种技术对于小型脚本尚可,但它有一个很大的缺陷:每次运行之前,你都必须在命令提示符下输入整个命令。可以将这些命令组合成一个简单的文本文件,这样就不需要在命令行中手动输入了。在需要运行这些命令时,只用运行这个文本文件就行了。
11.2 创建shell脚本文件
要将shell命令放到文本文件中,首先需要用文本编辑器(参见第10章)来创建一个文件,然后将命令输入到文件中。
在创建shell脚本文件时,必须在文件的第一行指定要使用的shell。其格式为:
#!/bin/bash
在通常的shell脚本中,井号(#)用作注释行。shell并不会处理shell脚本中的注释行。然而,shell脚本文件的第一行是个例外,#后面的惊叹号会告诉shell用哪个shell来运行脚本(是的,你可以使用bash shell,同时还可以使用另一个shell来运行你的脚本)。
在指定了shell之后,就可以在文件的每一行中输入命令,然后加一个回车符。之前提到过,注释可用#添加。例如:
#!/bin/bash
# This script displays the date and who's logged on date
who
这就是脚本的所有内容了。可以根据需要,使用分号将两个命令放在一行上,但在shell脚本中,你可以在独立的行中书写命令。shell会按根据命令在文件中出现的顺序进行处理。
还有,要注意另有一行也以#开头,并添加了一个注释。shell不会解释以#开头的行(除了以#!开头的第一行)。留下注释来说明脚本做了什么,这种方法非常好。当两年后回过来再看这个脚本时,你还可以很容易回忆起做过什么。
将这个脚本保存在名为test1的文件中,基本就好了。在运行新脚本前,还要做其他一些事。 现在运行脚本,结果可能会叫你有点失望。
$ test1
bash: test1: command not found
$
你要跨过的第一个障碍是让bash shell能找到你的脚本文件。如第6章所述,shell会通过PATH环境变量来查找命令。快速查看一下PATH环境变量就可以弄清问题所在。
$ echo $PATH
/usr/kerberos/sbin:/usr/kerberos/bin:/usr/local/bin:/usr/bin :/bin:/usr/local/sbin:/usr/sbin:/sbin:/home/user/bin
$
PATH环境变量被设置成只在一组目录中查找命令。要让shell找到test1脚本,只需采取以下两种作法之一:
将shell脚本文件所处的目录添加到PATH环境变量中;
在提示符中用绝对或相对文件路径来引用shell脚本文件。
窍门 有些Linux发行版将$HOME/bin目录添加进了PATH环境变量。它在每个用户的HOME目录下提供了一个存放文件的地方,shell可以在那里查找要执行的命令。
在这个例子中,我们将用第二种方式将脚本文件的确切位置告诉shell。记住,为了引用当前目录下的文件,可以在shell中使用单点操作符。
$ ./test1
bash: ./test1: Permission denied
$
现在shell找到了脚本文件,但还有一个问题。shell指明了你还没有执行文件的权限。快速查看一下文件权限就能找到问题所在。
$ ls -l test1
-rw-rw-r-- 1 user user 73 Sep 24 19:56 test1
$
在创建test1文件时,umask的值决定了新文件的默认权限设置。由于umask变量在Ubuntu中被设成了022(参见第7章),所以系统创建的文件只有文件属主和属组才有读/写权限。
下一步是通过chmod命令(参见第7章)赋予文件属主执行文件的权限。
$ chmod u+x test1
$ ./test1
Mon Feb 21 15:38:19 EST 2014
Christine tty2 2014-02-21 15:26
Samantha tty3 2014-02-21 15:26
Timothy tty1 2014-02-21 15:26
user tty7 2014-02-19 14:03 (:0)
user pts/0 2014-02-21 15:21 (:0.0)
$
成功了!现在万事俱备,只待执行新的shell脚本文件了。
11.3 显示消息
大多数shell命令都会产生自己的输出,这些输出会显示在脚本所运行的控制台显示器上。很多时候,你可能想要添加自己的文本消息来告诉脚本用户脚本正在做什么。可以通过echo命令来实现这一点。如果在echo命令后面加上了一个字符串,该命令就能显示出这个文本字符串。
$ echo This is a test This is a test
$
注意,默认情况下,不需要使用引号将要显示的文本字符串划定出来。但有时在字符串中出现引号的话就比较麻烦了。
$ echo Let's see if this'll work Lets see if thisll work
$
echo命令可用单引号或双引号来划定文本字符串。如果在字符串中用到了它们,你需要在文本中使用其中一种引号,而用另外一种来将字符串划定起来。
$ echo "This is a test to see if you're paying attention" This is a test to see if you're paying attention
$ echo 'Rich says "scripting is easy".'
Rich says "scripting is easy".
$
所有的引号都可以正常输出了。
可以将echo语句添加到shell脚本中任何需要显示额外信息的地方。
$ cat test1
#!/bin/bash
# This script displays the date and who's logged on echo The time and date are:
date
echo "Let's see who's logged into the system:"
who
$
当运行这个脚本时,它会产生如下输出。
$ ./test1
The time and date are:
Mon Feb 21 15:41:13 EST 2014
Let's see who's logged into the system:
Christine tty2 2014-02-21 15:26
Samantha tty3 2014-02-21 15:26
Timothy tty1 2014-02-21 15:26
user tty7 2014-02-19 14:03 (:0)
user pts/0 2014-02-21 15:21 (:0.0)
$
很好,但如果想把文本字符串和命令输出显示在同一行中,该怎么办呢?可以用echo语句的-n参数。只要将第一个echo语句改成这样就行:
echo -n "The time and date are: "
你需要在字符串的两侧使用引号,保证要显示的字符串尾部有一个空格。命令输出将会在紧接着字符串结束的地方出现。现在的输出会是这样:
$ ./test1
The time and date are: Mon Feb 21 15:42:23 EST 2014 Let's see who's logged into the system:
Christine tty2 2014-02-21 15:26
Samantha tty3 2014-02-21 15:26
Timothy tty1 2014-02-21 15:26
user tty7 2014-02-19 14:03 (:0)
user pts/0 2014-02-21 15:21 (:0.0)
$
完美!echo命令是shell脚本中与用户交互的重要工具。你会发现在很多地方都能用到它,尤其是需要显示脚本中变量的值的时候。我们下面继续了解这个。
11.4 使用变量
运行shell脚本中的单个命令自然有用,但这有其自身的限制。通常你会需要在shell命令使用其他数据来处理信息。这可以通过变量来实现。变量允许你临时性地将信息存储在shell脚本中,以便和脚本中的其他命令一起使用。本节将介绍如何在shell脚本中使用变量。
11.4.1 环境变量
你已经看到过Linux的一种变量在实际中的应用。第6章介绍了Linux系统的环境变量。也可以在脚本中访问这些值。
shell维护着一组环境变量,用来记录特定的系统信息。比如系统的名称、登录到系统上的用户名、用户的系统ID(也称为UID)、用户的默认主目录以及shell查找程序的搜索路径。可以用set命令来显示一份完整的当前环境变量列表。
$ set
BASH=/bin/bash
[...]
HOME=/home/Samantha
HOSTNAME=localhost.localdomain
HOSTTYPE=i386
IFS=$' \t\n'
IMSETTINGS_INTEGRATE_DESKTOP=yes
IMSETTINGS_MODULE=none
LANG=en_US.utf8
LESSOPEN='|/usr/bin/lesspipe.sh %s' LINES=24
LOGNAME=Samantha
[...]
在脚本中,你可以在环境变量名称之前加上美元符($)来使用这些环境变量。下面的脚本演示了这种用法。
$ cat test2
#!/bin/bash
# display user information from the system. echo "User info for userid: $USER"
echo UID: $UID
echo HOME: $HOME
$
U S E R 、 USER、 USER、UID和$HOME环境变量用来显示已登录用户的有关信息。脚本输出如下:
$chmod u+x test2
$ ./test2
User info for userid: Samantha UID: 1001
HOME: /home/Samantha
$
注意,echo命令中的环境变量会在脚本运行时替换成当前值。另外,在第一个字符串中可以将$USER系统变量放置到双引号中,而shell依然能够知道我们的意图。但采用这种方法也有一个问题。看看下面这个例子会怎么样。
$ echo "The cost of the item is $15" The cost of the item is 5
显然这不是我们想要的。只要脚本在引号中出现美元符,它就会以为你在引用一个变量。在这个例子中,脚本会尝试显示变量$1(但并未定义),再显示数字5。要显示美元符,你必须在它前面放置一个反斜线。
$ echo "The cost of the item is \$15" The cost of the item is $15
看起来好多了。反斜线允许shell脚本将美元符解读为实际的美元符,而不是变量。下一节将会介绍如何在脚本中创建自己的变量。
说明 你可能还见过通过${variable}形式引用的变量。变量名两侧额外的花括号通常用来帮
助识别美元符后的变量名。
11.4.2 用户变量
除了环境变量,shell脚本还允许在脚本中定义和使用自己的变量。定义变量允许临时存储数据并在整个脚本中使用,从而使shell脚本看起来更像一个真正的计算机程序。
用户变量可以是任何由字母、数字或下划线组成的文本字符串,长度不超过20个。用户变量区分大小写,所以变量Var1和变量var1是不同的。这个小规矩经常让脚本编程初学者感到头疼。
使用等号将值赋给用户变量。在变量、等号和值之间不能出现空格(另一个困扰初学者的用法)。这里有一些给用户变量赋值的例子。
var1=10
var2=-57
var3=testing
var4="still more testing"
shell脚本会自动决定变量值的数据类型。在脚本的整个生命周期里,shell脚本中定义的变量会一直保持着它们的值,但在shell脚本结束时会被删除掉。
与系统变量类似,用户变量可通过美元符引用。
$ cat test3 #!/bin/bash
# testing variables
days=10
guest="Katie"
echo "$guest checked in $days days ago" days=5
guest="Jessica"
echo "$guest checked in $days days ago"
$
运行脚本会有如下输出。
$ chmod u+x test3
$ ./test3
Katie checked in 10 days ago
Jessica checked in 5 days ago
$
变量每次被引用时,都会输出当前赋给它的值。重要的是要记住,引用一个变量值时需要使用美元符,而引用变量来对其进行赋值时则不要使用美元符。通过一个例子你就能明白我的意思。
$ cat test4
#!/bin/bash
# assigning a variable value to another variable
value1=10
value2=$value1
echo The resulting value is $value2
$
在赋值语句中使用value1变量的值时,仍然必须用美元符。这段代码产生如下输出。
$ chmod u+x test4
$ ./test4
The resulting value is 10
$
要是忘了用美元符,使得value2的赋值行变成了这样:
value2=value1
那你会得到如下输出:
$ ./test4
The resulting value is value1
$
没有美元符,shell会将变量名解释成普通的文本字符串,通常这并不是你想要的结果。
11.4.3 命令替换
shell脚本中最有用的特性之一就是可以从命令输出中提取信息,并将其赋给变量。把输出赋给变量之后,就可以随意在脚本中使用了。这个特性在处理脚本数据时尤为方便。
有两种方法可以将命令输出赋给变量:
反引号字符(`)
$()格式
要注意反引号字符,这可不是用于字符串的那个普通的单引号字符。由于在shell脚本之外很少用到,你可能甚至都不知道在键盘什么地方能找到这个字符。但你必须慢慢熟悉它,因为这是许多shell脚本中的重要组件。提示:在美式键盘上,它通常和波浪线(~)位于同一键位。
命令替换允许你将shell命令的输出赋给变量。尽管这看起来并不那么重要,但它却是脚本编程中的一个主要组成部分。
要么用一对反引号把整个命令行命令围起来:
testing='date'
要么使用$()格式:
testing=$(date)
shell会运行命令替换符号中的命令,并将其输出赋给变量testing。注意,赋值等号和命令替换字符之间没有空格。这里有个使用普通的shell命令输出创建变量的例子。
$ cat test5
#!/bin/bash
testing=$(date)
echo "The date and time are: " $testing
$
变量testing获得了date命令的输出,然后使用echo语句显示出它的值。运行这个shell脚本生成如下输出。
$ chmod u+x test5
$ ./test5
The date and time are: Mon Jan 31 20:23:25 EDT 2014
$
这个例子毫无吸引人的地方(也可以干脆将该命令放在echo语句中),但只要将命令的输出放到了变量里,你就可以想干什么就干什么了。
下面这个例子很常见,它在脚本中通过命令替换获得当前日期并用它来生成唯一文件名。
#!/bin/bash
# copy the /usr/bin directory listing to a log file today=$(date +%y%m%d)
ls /usr/bin -al > log.$today
today变量是被赋予格式化后的date命令的输出。这是提取日期信息来生成日志文件名常用的一种技术。+%y%m%d格式告诉date命令将日期显示为两位数的年月日的组合。
$ date +%y%m%d 140131
$
这个脚本将日期值赋给一个变量,之后再将其作为文件名的一部分。文件自身含有目录列表的重定向输出(将在11.5节详细讨论)。运行该脚本之后,应该能在目录中看到一个新文件。
-rw-r--r-- 1 user user 769 Jan 31 10:15 log.140131
目录中出现的日志文件采用$today变量的值作为文件名的一部分。日志文件的内容是/usr/bin目录内容的列表输出。如果脚本在明天运行,日志文件名会是log.140201,就这样为新的一天创建一个新文件。
警告 命令替换会创建一个子shell来运行对应的命令。子shell(subshell)是由运行该脚本的shell所创建出来的一个独立的子shell(child shell)。正因如此,由该子shell所执行命令是无法使用脚本中所创建的变量的。
在命令行提示符下使用路径./运行命令的话,也会创建出子shell;要是运行命令的时候不加入路径,就不会创建子shell。如果你使用的是内建的shell命令,并不会涉及子shell。在命令行提示符下运行脚本时一定要留心!
11.5 重定向输入和输出
有些时候你想要保存某个命令的输出而不仅仅只是让它显示在显示器上。bash shell提供了几个操作符,可以将命令的输出重定向到另一个位置(比如文件)。重定向可以用于输入,也可以用于输出,可以将文件重定向到命令输入。本节介绍了如何在shell脚本中使用重定向。
11.5.1 输出重定向
最基本的重定向将命令的输出发送到一个文件中。bash shell用大于号(>)来完成这项功能:
command > outputfile
之前显示器上出现的命令输出会被保存到指定的输出文件中。
$ date > test6
$ ls -l test6
-rw-r--r-- 1 user user 29 Feb 10 17:56 test6 $ cat test6
Thu Feb 10 17:56:58 EDT 2014
$
重定向操作符创建了一个文件test6(通过默认的umask设置),并将date命令的输出重定向到该文件中。如果输出文件已经存在了,重定向操作符会用新的文件数据覆盖已有文件。
$ who > test6
$ cat test6
user pts/0 Feb 10 17:55
$
现在test6文件的内容就是who命令的输出。
有时,你可能并不想覆盖文件原有内容,而是想要将命令的输出追加到已有文件中,比如你正在创建一个记录系统上某个操作的日志文件。在这种情况下,可以用双大于号(>>)来追加数据。
$ date >> test6
$ cat test6
user pts/0 Feb 10 17:55 Thu Feb 10 18:02:14 EDT 2014
$
test6文件仍然包含早些时候who命令的数据,现在又加上了来自date命令的输出。
11.5.2 输入重定向
输入重定向和输出重定向正好相反。输入重定向将文件的内容重定向到命令,而非将命令的输出重定向到文件。
输入重定向符号是小于号(<):
command < inputfile
一个简单的记忆方法就是:在命令行上,命令总是在左侧,而重定向符号“指向”数据流动的方向。小于号说明数据正在从输入文件流向命令。
这里有个和wc命令一起使用输入重定向的例子。
$ wc < test6
2 11 60
$
wc命令可以对对数据中的文本进行计数。默认情况下,它会输出3个值:
文本的行数
文本的词数
文本的字节数
通过将文本文件重定向到wc命令,你立刻就可以得到文件中的行、词和字节的计数。这个例子说明test6文件有2行、11个单词以及60字节。
还有另外一种输入重定向的方法,称为内联输入重定向(inline input redirection)。这种方法无需使用文件进行重定向,只需要在命令行中指定用于输入重定向的数据就可以了。乍看一眼,这可能有点奇怪,但有些应用会用到这种方式(参见11.7节)。
内联输入重定向符号是远小于号(<<)。除了这个符号,你必须指定一个文本标记来划分输入数据的开始和结尾。任何字符串都可作为文本标记,但在数据的开始和结尾文本标记必须一致。
command << marker data
marker
在命令行上使用内联输入重定向时,shell会用PS2环境变量中定义的次提示符(参见第6章)来提示输入数据。下面是它的使用情况。
$ wc << EOF
> test string 1 > test string 2 > test string 3 > EOF
3 9 42
$
提示符会持续提示,以获取更多的输入数据,直到你输入了作为文本标记的那个字符串。wc命令会对内联输入重定向提供的数据进行行、词和字节计数。
小结:
Shell是一种强大的脚本语言,在Linux和Unix系统中广泛使用。脚本是一种方便的编程方式,可以用来自动化任务和批处理。在Shell脚本中,变量和重定向是非常常见的概念。
变量
在Shell脚本中,变量是一种存储值的方式。变量可以存储文本、数字和其他类型的数据。在Shell中,使用 符号来引用变量。例如, 符号来引用变量。例如, 符号来引用变量。例如,myvar表示myvar变量的值。
使用变量的好处是可以动态地传递值,使脚本更加灵活。例如,在脚本中使用变量代替常量可以使脚本在不同环境中具有可移植性。
创建变量的语法是:
变量名=值
例如,创建一个名为myvar的变量并将其设置为hello:
myvar=hello
使用变量时,需要在变量名前加上$符号。例如,使用myvar变量的值:
echo $myvar
这将输出hello。
在Shell中,还有一些特殊的变量。例如,$0表示脚本的名称,$1表示第一个参数,$2表示第二个参数,以此类推。这些变量可以在脚本中使用,以便获取脚本所需的信息。
重定向输入和输出
重定向是一种将命令的输入和输出重定向到文件或其他命令的过程。在Shell中,可以使用重定向来处理文件、管道和命令。
重定向输出是将命令的标准输出从屏幕输出到文件或其他命令中。可以使用>符号将输出重定向到文件中。例如,将ls命令的输出重定向到文件中:
ls > file.txt
这将在当前目录下创建一个名为file.txt的文件,并将ls命令的输出写入该文件中。如果file.txt文件已经存在,则该文件将被覆盖。
在Shell中,还可以使用>>符号将输出附加到文件中。例如,将date命令的输出附加到文件中:
date >> file.txt
这将在file.txt文件中添加当前日期和时间。
重定向输入是将命令从文件或管道中获取输入。可以使用<符号将命令的输入从文件中获取。例如,使用cat命令显示文件的内容:
cat file.txt
也可以使用<符号将文件的内容作为命令的输入:
cat < file.txt
在Shell中,还可以使用管道(|)将一个命令的输出作为另一个命令的输入。例如,将ls命令的输出作为grep命令的输入:
ls | grep file
这将在当前目录中列出所有包含“file”字符串的文件。
总结
在Shell脚本中,变量和重定向是非常常见的概念。通过使用变量,可以动态地传递值,使脚本更加灵活。通过使用重定向,可以处理文件、管道和命令,使脚本更加强大。熟练掌握变量和重定向的使用,可以使Shell脚本开发更加高效和便捷。