1 Bash 和 Bash 脚本 #
现今,许多人都在使用装有 GNOME 之类图形用户界面 (GUI) 的计算机。尽管它们提供了大量功能,但使用它们执行自动任务时,还是会有限制。外壳是对 GUI 的很好补充,本章提供关于外壳(在本例中为 Bash)的某些方面的概述。
1.1 什么是“外壳”? #
通常来说,外壳就是指 Bash(Bourne again 外壳)。在本章中提到“外壳”时,指的是 Bash。事实上,除了 Bash 还存在很多其他外壳(ash、csh、ksh、zsh 等),每种外壳都具备不同的功能和特征。如果需要关于其他外壳的更多信息,请在 YaST 中搜索外壳。
1.1.1 了解 Bash 配置文件 #
外壳可调用为:
- 交互式登录外壳: 当登录计算机时需要使用此方式,即使用 - --login选项调用 Bash 或通过 SSH 登录到远程计算机时。
- “普通”交互式外壳: 这通常在启动 xterm、konsole、gnome 终端或类似工具时使用。 
- 非交互式外壳: 当在命令行调用外壳脚本时使用。 
根据所用外壳的类型,会读取不同的配置文件。下表显示登录和非登录外壳的配置文件。
| 文件 | 描述 | 
|---|---|
| 
 | 不要修改此文件,否则在下一次更新时可能损坏您的修改! | 
| 
 | 
         如果扩展  | 
| 
 | 包含特定程序的系统范围配置文件 | 
| 
 | 在此处插入特定于用户的登录外壳配置 | 
请注意,登录外壳还会获取表 1.2 “非登录外壳的 Bash 配置文件”中所列的配置文件。
| 
 | 不要修改此文件,否则在下一次更新时可能损坏您的修改! | 
| 
 | 使用此文件插入系统范围的修改(仅 Bash) | 
| 
 | 在此处插入特定于用户的配置 | 
此外,Bash 还使用更多文件:
| 文件 | 描述 | 
|---|---|
| 
 | 包含已键入的所有命令的列表 | 
| 
 | 注销时执行 | 
| 
 | 
         用户为常用命令定义的别名。有关如何定义别名的详细信息,请参见  | 
1.1.2 目录结构 #
下表简要介绍 Linux 系统上最重要的较高级别目录。以下列表中是关于这些目录和重要子目录的更多详细信息。
| 目录 | 内容 | 
|---|---|
| 
 | 根目录 — 目录树的起点。 | 
| 
 | 基本二进制文件,例如系统管理员和普通用户都需要的命令。通常还包含外壳,如 Bash。 | 
| 
 | 引导加载程序的静态文件。 | 
| 
 | 访问特定于主机的设备所需的文件。 | 
| 
 | 特定于主机的系统配置文件。 | 
| 
 | 
       储存系统上具有帐户的所有用户的用户主目录。但是, | 
| 
 | 基本共享库和内核模块。 | 
| 
 | 可卸媒体的安装点。 | 
| 
 | 临时装入文件系统的安装点。 | 
| 
 | 附加应用程序软件包。 | 
| 
 | 
       超级用户  | 
| 
 | 基本系统二进制文件。 | 
| 
 | 系统提供的服务的数据。 | 
| 
 | 临时文件。 | 
| 
 | 具有只读数据的辅助层次结构。 | 
| 
 | 变量数据,如日志文件。 | 
| 
 | 只在系统上同时安装了 Microsoft Windows* 和 Linux 时可用。包含 Windows 数据。 | 
以下列表提供有关这些目录中有哪些文件和子目录的更多详细信息,并给出一些示例:
- /bin
- 包含 - root和其他用户都可使用的基本 shell 命令。这些命令包括- ls、- mkdir、- cp、- mv、- rm和- rmdir。- /bin也包含 Bash,它是 SUSE Linux Enterprise Server 中的默认外壳。
- /boot
- 包含引导所需的数据,如引导加载程序、内核以及内核开始执行用户模式程序之前使用的其他数据。 
- /dev
- 储存代表硬件组件的设备文件。 
- /etc
- 包含控制诸如 X Window 系统等程序操作的本地配置文件。 - /etc/init.d子目录包含引导过程中可执行的 LSB init 脚本。
- /home/USERNAME
- 储存在系统中建立帐户的所有用户的私人数据。这里的文件只能由其拥有者或系统管理员修改。默认情况下,电子邮件目录和个人桌面配置以隐藏文件和目录的形式储存在此处,例如 - .gconf/和- .config。注意:网络环境中的用户主目录- 如果在网络环境中工作,则您的用户主目录可能映射到文件系统中除 - /home之外的其他目录中。
- /lib
- 包含引导系统和运行 root 文件系统中的命令所需的基本共享库。共享库相当于 Windows 中的 DLL 文件。 
- /media
- 包含 CD-ROM、闪存盘和数码相机(如果它们使用 USB)等可卸媒体的安装点。 - /media通常包含除系统硬盘之外的各类驱动器。可卸媒体插入或连接到系统并装入之后,您便可从此处访问该媒体。
- /mnt
- 此目录提供临时装入的文件系统的安装点。 - root可以在此处装入文件系统。
- /opt
- 保留用于安装第三方软件。在此处可以找到可选软件和较大附加产品程序包。 
- /root
- root用户的主目录。- root的个人数据储存在此处。
- /run
- systemd和各个组件使用的 tmpfs 目录。- /var/run是- /run的符号链接。
- /sbin
- 如 - s所表明的,该目录储存超级用户的实用程序。- /sbin包含- /bin中的二进制文件以及引导和恢复系统所需的其他二进制文件。
- /srv
- 储存系统提供的服务(如 FTP 和 HTTP)的数据。 
- /tmp
- 此目录由需要临时储存文件的程序使用。 重要:在引导时清理- /tmp- 系统重引导后,之前储存在 - /tmp中的数据无法保证仍然存在。这视情况而定,例如,取决于- /etc/tmpfiles.d/tmp.conf中所做的设置。
- /usr
- /usr与用户无关,而是 Unix 系统资源 (Unix system resource) 的缩写。- /usr中的数据是可以在符合- 文件系统层次标准(FHS) 的各个主机之间共享的静态只读数据。此目录包含所有应用程序(包括 GNOME 等图形桌面),并且会在文件系统中创建次要层次。- /usr储存了多个子目录,例如- /usr/bin、- /usr/sbin、- /usr/local和- /usr/share/doc。
- /usr/bin
- 包含一般可访问的程序。 
- /usr/sbin
- 包含为系统管理员保留的程序,例如维修功能。 
- /usr/local
- 在此目录中,系统管理员可以安装本地的独立于分发包的扩展。 
- /usr/share/doc
- 储存系统的各种文档文件和发行描述。在 - manual子目录中可以找到此手册的联机版本。如果安装了多种语言,则此目录可能包含这些手册不同语言的版本。- 在 - packages下可以找到系统上安装的软件包中包含的文档。对于每个包,都会创建一个子目录- /usr/share/doc/packages/PACKAGENAME,通常用其储存该包的自述文件,有时储存示例、配置文件或附加脚本。- 如果系统上安装了操作指南, - /usr/share/doc中还会包含- howto子目录,该目录中提供与 Linux 软件安装和操作相关的许多任务的附加文档。
- /var
- /usr用于储存静态只读的数据,而- /var用于在系统操作期间写入并成为变量数据的数据,例如日志文件或假脱机数据。有关最重要日志文件的概述,可以在- /var/log/下找到,请参见见表 41.1 “日志文件”。
1.2 编写外壳脚本 #
使用外壳脚本可以方便地完成各种任务:收集数据、在文本中搜索单词或短语,以及执行其他有用的操作。以下示例显示用于打印文本的小外壳脚本:
可以运行该脚本之前,需要一些先决条件:
- 每个脚本都应包含 Shebang 行(如上面的示例所示)。如果该行缺失,您需要手动调用解释器。 
- 可以将该脚本保存在任何位置。但是,建议将其保存在外壳可以找到的目录中。外壳中的搜索路径由环境变量 - PATH确定。通常,一般用户不具有对- /usr/bin的写权限。因此,建议将脚本保存在用户目录- ~/bin/中。在上例中使用名称- hello.sh。
- 该脚本需要可执行权限。使用以下命令设置权限: - chmod +x ~/bin/hello.sh 
如果已满足上述所有先决条件,则可以按如下方式执行此脚本:
- 作为绝对路径: 可以使用绝对路径执行脚本。在本例中为 - ~/bin/hello.sh。
- 所有位置: 如果 - PATH环境变量包含脚本所在目录,则可以使用- hello.sh来执行该脚本。
1.3 重定向命令事件 #
每个命令都可以使用三个通道输入或输出:
- 标准输出: 这是默认的输出通道。在命令打印某些内容时都会使用标准输出通道。 
- 标准输入: 如果一个命令需要用户或其他命令输入,则使用此通道。 
- 标准错误: 命令使用此通道报告错误。 
要重定向这些通道,有以下可行的操作方式:
- 命令 > 文件
- 将该命令的输出保存为文件,将删除现有文件。例如, - ls命令会将其输出写入文件- listing.txt:- ls > listing.txt 
- 命令 >> 文件
- 将命令输出追加到文件。例如, - ls命令会将其输出追加到文件- listing.txt:- ls >> listing.txt 
- 命令 < 文件
- 读取该文件作为给定命令的输入。例如, - read命令会将此文件的内容读入变量:- read a < foo 
- 命令 1 | 命令 2
- 将左侧命令的输出重定向为右侧命令的输入。例如, - cat命令会输出文件- /proc/cpuinfo的内容。- grep使用此输出仅过滤出包含- cpu的行:- cat /proc/cpuinfo | grep cpu 
   每个通道都有一个文件描述符:0(零)表示标准输入,1 表示标准输出,2 表示标准错误。允许在 < 或 > 字符前插入此文件描述符。例如,以下行搜索以 foo 开头的文件,但通过将错误重定向到 /dev/null 而抑制错误。
  
find / -name "foo*" 2>/dev/null
1.4 使用别名 #
别名是一个或多个命令的快捷方式定义。别名的语法为:
alias NAME=DEFINITION
   例如,下行定义了一个别名 lt,它会输出一份较长的列表(选项 -l),将其按修改时间排序 (-t),并按排好序的倒序打印 (-r):
  
alias lt='ls -ltr'
   要查看所有别名定义,请使用 alias。使用 unalias 和相应的别名删除别名。
  
1.5 在 Bash 中使用变量 #
外壳变量可以是全局变量,也可以是局部变量。全局变量(或环境变量)可以在所有外壳中访问。而局部变量仅在当前外壳中可见。
   要查看所有环境变量,请使用 printenv 命令。如果需要知道变量的值,请将变量名称作为自变量插入:
  
printenv PATH
   也可以使用 echo 查看变量(无论是全局或本地变量):
  
echo $PATH
要设置局部变量,请使用变量名后加等号和值:
PROJECT="SLED"
   不要在等号两边插入空格,否则会出错。要设置环境变量,请使用 export:
  
export NAME="tux"
   要删除变量,请使用 unset:
  
unset NAME
下表包含外壳脚本中可以使用的一些常见环境变量:
| 
 | 当前用户的用户主目录 | 
| 
 | 当前主机名 | 
| 
 | 
        当一个工具本地化后,它使用此环境变量中的语言。英语也可以设置为  | 
| 
 | 外壳的搜索路径,冒号分隔的目录列表 | 
| 
 | 指定在每个命令前打印的普通提示符 | 
| 
 | 指定在执行多行命令时打印的辅助提示符 | 
| 
 | 当前工作目录 | 
| 
 | 当前用户 | 
1.5.1 使用自变量 #
    例如,如果具有脚本 foo.sh,则可以如下执行:
   
foo.sh "Tux Penguin" 2000
    要访问传递给脚本的所有自变量,您需要定位参数。$1 表示第一个自变量,$2 表示第二个自变量,依此类推。至多可以有九个参数。要获取脚本名称,请使用 $0。
   
    以下脚本 foo.sh 打印从 1 到 4 的所有自变量:
   
#!/bin/sh echo \"$1\" \"$2\" \"$3\" \"$4\"
如果使用上述自变量执行此脚本,将获取:
"Tux Penguin" "2000" "" ""
1.5.2 使用变量替换 #
变量替换将一个模式应用于变量的内容(从左侧或从右侧)。以下列表包含可能的语法格式:
- ${VAR#pattern}
- 从左侧删除可能的最短匹配: - file=/home/tux/book/book.tar.bz2 echo ${file#*/} home/tux/book/book.tar.bz2
- ${VAR##pattern}
- 从左侧删除可能的最长匹配: - file=/home/tux/book/book.tar.bz2 echo ${file##*/} book.tar.bz2
- ${VAR%pattern}
- 从右侧删除可能的最短匹配: - file=/home/tux/book/book.tar.bz2 echo ${file%.*} /home/tux/book/book.tar
- ${VAR%%pattern}
- 从右侧删除可能的最长匹配: - file=/home/tux/book/book.tar.bz2 echo ${file%%.*} /home/tux/book/book
- ${VAR/pattern_1/pattern_2}
- 将来自 PATTERN_1 的 VAR 的内容替代为 PATTERN_2: - file=/home/tux/book/book.tar.bz2 echo ${file/tux/wilber} /home/wilber/book/book.tar.bz2
1.6 将命令分组和组合 #
外壳允许您对命令执行连接和分组以有条件地执行。每个命令都返回一个退出码,该退出码确定操作是成功还是失败。如果是 0,则命令成功,任何其他值都表示特定于该命令的一个错误。
以下列表显示可以如何将命令分组:
- 命令 1 ; 命令 2
- 顺序地执行这些命令。不检查退出码。以下行使用 - cat显示文件的内容,然后使用- ls打印其文件属性,而不考虑退出码:- cat filelist.txt ; ls -l filelist.txt 
- 命令 1 && 命令 2
- 如果左侧命令成功,则运行右侧命令(逻辑运算符 AND)。仅当上一个命令成功时,以下行才显示文件的内容并打印其文件属性(将其与列表中的上一项相比较): - cat filelist.txt && ls -l filelist.txt 
- 命令 1 || 命令 2
- 当左侧命令失败时运行右侧命令(逻辑运算符 OR)。以下行仅当在 - /home/tux/foo中创建目录失败时才会在- /home/wilber/bar中创建目录:- mkdir /home/tux/foo || mkdir /home/wilber/bar 
- funcname(){ ... }
- 创建外壳函数。您可以使用定位参数访问其自变量。以下行定义用于打印短消息的函数 - hello:- hello() { echo "Hello $1"; }- 您可以如下调用此函数: - hello Tux - 它会打印: - Hello Tux 
1.7 使用通用流程构造语句 #
   为了控制脚本的流程,外壳有 while、if、for 和 case 等构造语句。
  
1.7.1 if 控制命令 #
if 命令用于检查表达式。例如,以下代码测试当前用户是否是 Tux:
   
if test $USER = "tux"; then echo "Hello Tux." else echo "You are not Tux." fi
    测试表达式既可以复杂也可以简单。以下表达式检查文件 foo.txt 是否存在:
   
if test -e /tmp/foo.txt ; then echo "Found foo.txt" fi
测试表达式也可以缩写在角括号中:
if [ -e /tmp/foo.txt ] ; then echo "Found foo.txt" fi
在 http://www.cyberciti.biz/nixcraft/linux/docs/uniqlinuxfeatures/lsst/ch03sec02.html 上可以找到更多有用表达式。
1.7.2 使用 for 命令创建循环 #
for 循环允许您对一系列项执行命令。例如,以下代码打印关于当前工作目录中 PNG 文件的某些信息:
   
for i in *.png; do ls -l $i done
1.8 更多信息 #
   关于 Bash 的重要信息在手册页 man bash 中提供。可以在以下列表中找到关于此主题的更多信息:
  
- http://tldp.org/LDP/Bash-Beginners-Guide/html/index.html — Bash 入门者指南 
- http://tldp.org/HOWTO/Bash-Prog-Intro-HOWTO.html — BASH 编程 - 简介操作指南 
- http://tldp.org/LDP/abs/html/index.html — 高级 Bash 脚本编写指南 
- http://www.grymoire.com/Unix/Sh.html — Sh - Bourne 外壳