documentation.suse.com / SUSE Linux Enterprise Desktop 文档 / 管理指南 / 常用任务 / Bash 和 Bash 脚本
适用范围 SUSE Linux Enterprise Desktop 15 SP7

1 Bash 和 Bash 脚本

现今,许多人都在使用装有 GNOME 之类图形用户界面 (GUI) 的计算机。尽管 GUI 可提供许多功能,但执行自动化任务时,这些功能会受到限制。外壳是对 GUI 的有效补充,本章将会概述外壳(以 Bash 外壳为例)的数个方面。

1.1 什么是外壳

通常来说,Linux 外壳就是 Bash (Bourne again Shell)。在本章中提到外壳时,指的是 Bash。除了 Bash 以外,还存在其他一些外壳(ash、csh、ksh、zsh 等),每种外壳都具备不同的功能和特征。

1.1.1 Bash 配置文件

外壳可调用为:

  1. 交互式登录外壳: 使用 --login 选项调用 Bash 来登录计算机时,或通过 SSH 登录到远程计算机时,需要使用此方式。

  2. 交互式非登录外壳: 在启动 xterm、konsole、gnome-terminal 或类似的命令行界面 (CLI) 工具时通常会调用此外壳。

  3. 非交互式非登录外壳: 当在命令行调用外壳脚本时调用此外壳。

各个外壳会读取不同的配置文件。下表显示登录和非登录外壳的配置文件。

提示
提示

Bash 会根据运行配置文件的外壳类型按特定顺序查找其配置文件。有关详细信息,请参见 Bash 手册页 (man 1 bash)。搜索标题 INVOCATION

表 1.1︰ 登录外壳的 Bash 配置文件

文件

说明

/etc/profile

请勿修改此文件,否则下次更新时,您的修改可能会被损坏。

/etc/profile.local

如果要扩展 /etc/profile,请使用此文件

/etc/profile.d/

包含特定程序的系统范围配置文件

~/.profile

在此处插入特定于用户的登录外壳配置

登录外壳还会获取表 1.2 “非登录外壳的 Bash 配置文件”中所列的配置文件。

表 1.2︰ 非登录外壳的 Bash 配置文件

/etc/bash.bashrc

请勿修改此文件,否则下次更新时,您的修改可能会被损坏。

/etc/bash.bashrc.local

使用此文件插入系统范围的修改(仅 Bash)

~/.bashrc

在此处插入特定于用户的配置

Bash 还会使用其他多个文件:

表 1.3︰ 用于 Bash 的特殊文件

文件

说明

~/.bash_history

包含已键入的所有命令的列表

~/.bash_logout

注销时执行

~/.alias

用户为常用命令定义的别名。有关定义别名的更多细节,请参见 man 1 alias

非登录外壳

下面两个特殊外壳会阻止用户登录到系统:/bin/false/sbin/nologin。当用户尝试登录到系统时,这两个外壳都将失败且不会显示任何提示。这是针对系统用户有意设计的一种安全措施,虽然新式 Linux 操作系统提供了更有效的工具(例如 PAM 和 AppArmor)来控制系统访问。

SUSE Linux Enterprise Desktop 上的默认行为是将 /bin/bash 分配给人类用户,将 /bin/false/sbin/nologin 分配给系统用户。但由于历史原因,nobody 用户拥有 /bin/bash,因为该用户过去是默认用做系统用户的最低特权用户。但是,如果有多个系统用户使用 nobody,则使用 nobody 而获得的任何一点安全性都将失去。应该可以将它更改为 /sbin/nologin;最快的测试方法是进行此更改,然后看看这样是否中断了任何服务或应用程序。

使用以下命令列出 /etc/passwd 中已指派给所有用户(系统用户和人类用户)的外壳。输出视您系统上的服务和用户而异:

> sort -t: -k 7 /etc/passwd | awk -F: '{print $1"\t" $7}' | column -t
tux               /bin/bash
nobody            /bin/bash
root              /bin/bash
avahi             /bin/false
chrony            /bin/false
dhcpd             /bin/false
dnsmasq           /bin/false
ftpsecure         /bin/false
lightdm           /bin/false
mysql             /bin/false
postfix           /bin/false
rtkit             /bin/false
sshd              /bin/false
tftp              /bin/false
unbound           /bin/false
bin               /sbin/nologin
daemon            /sbin/nologin
ftp               /sbin/nologin
lp                /sbin/nologin
mail              /sbin/nologin
man               /sbin/nologin
nscd              /sbin/nologin
polkitd           /sbin/nologin
pulse             /sbin/nologin
qemu              /sbin/nologin
radvd             /sbin/nologin
rpc               /sbin/nologin
statd             /sbin/nologin
svn               /sbin/nologin
systemd-coredump  /sbin/nologin
systemd-network   /sbin/nologin
systemd-timesync  /sbin/nologin
usbmux            /sbin/nologin
vnc               /sbin/nologin
wwwrun            /sbin/nologin
messagebus        /usr/bin/false
scard             /usr/sbin/nologin

1.1.2 目录结构

下表简要介绍 Linux 系统上最重要的较高级别目录。以下列表中是关于这些目录和重要子目录的更多详细信息。

表 1.4︰ 标准目录树概述

目录

目录

/

根目录 — 目录树的起点。

/bin

基本二进制文件,例如系统管理员和普通用户都需要的命令。通常还包含外壳,如 Bash。

/boot

引导加载程序的静态文件。

/dev

访问特定于主机的设备所需的文件。

/etc

特定于主机的系统配置文件。

/home

存储系统上具有帐户的所有用户的用户主目录。但是,root 的主目录不在 /home 中,而是在 /root 中。

/lib

基本共享库和内核模块。

/media

可卸媒体的挂载点。

/mnt

临时挂载文件系统的挂载点。

/opt

附加应用程序软件包。

/root

超级用户 root 的主目录。

/sbin

基本系统二进制文件。

/srv

系统提供的服务的数据。

/tmp

临时文件。

/usr

具有只读数据的辅助层次结构。

/var

变量数据,如日志文件。

/windows

只在系统上同时安装了 Microsoft Windows* 和 Linux 时可用。包含 Windows 数据。

以下列表提供有关这些目录中有哪些文件和子目录的更多详细信息,并给出一些示例:

/bin

包含 root 和其他用户都可使用的基本外壳命令。这些命令包含 lsmkdircpmvrmrmdir/bin 还包含 Bash,后者是 SUSE Linux Enterprise Desktop 中的默认外壳。

/boot

包含引导所需的数据,如引导加载程序、内核以及内核开始执行用户模式程序之前使用的其他数据。

/dev

存储代表硬件组件的设备文件。

/etc

包含控制诸如 X Window 系统等程序操作的本地配置文件。/etc/init.d 子目录包含可在引导过程中执行的 LSB init 脚本。

/home/USERNAME

存储在系统中建立帐户的所有用户的私人数据。这里的文件只能由其拥有者或系统管理员修改。默认情况下,电子邮件目录和个人桌面配置以隐藏文件和目录的形式存放在此处,例如 .gconf/.config

注意
注意:网络环境中的用户主目录

如果您是在网络环境中工作,您的用户主目录可能会映射到文件系统中除 /home 之外的其他目录。

/lib

包含引导系统和运行根文件系统中的命令所需的基本共享库。共享库相当于 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 中的数据是静态只读数据,可以在符合 Filesystem Hierarchy Standard (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/ 中包含的最重要的日志文件,请参见表 42.1 “日志文件”

/windows

只在系统上同时安装了 Microsoft Windows 和 Linux 时可用。包含系统的 Windows 分区上可用的 Windows 数据。是否可以编辑此目录中的数据取决于 Windows 分区使用的文件系统。如果是 FAT32,则您可以打开和编辑此目录中的文件。对于 NTFS,SUSE Linux Enterprise Desktop 还包含写访问权限支持。但是,NTFS-3g 文件系统的驱动程序只拥有部分功能。

1.2 编写外壳脚本

使用外壳脚本可以方便地完成各种任务:收集数据、在文本中搜索单词或短语,以及执行其他有用的操作。以下示例显示用于打印文本的小外壳脚本:

例 1.1︰ 用于打印文本的外壳脚本
#!/bin/sh 1
# Output the following line: 2
echo "Hello World" 3

1

第一行以 Shebang 字符 (#!) 开头,表示此文件为脚本。在 Shebang 后面指定的解释器将执行该脚本。在本例中,指定的解释器为 /bin/sh

2

第二行是一个以哈希符号开头的注释。我们建议对难以理解的行提供注释。提供适当的注释可以记住该行的用途和功能。另外,其他阅读者可以更好地理解您的脚本。在开发社区中,注释被视为一种良好的做法。

3

第三行使用内置命令 echo 打印相应文本。

在运行此脚本之前,必须满足几个先决条件:

  1. 每个脚本都应包含 Shebang 行(如上面的示例所示)。如果该行缺失,您需要手动调用解释器。

  2. 可以将该脚本保存在任何位置。但是,建议将其保存在外壳可以找到的目录中。外壳中的搜索路径由环境变量 PATH 确定。普通用户对 /usr/bin 没有写入权限。因此,建议将脚本保存在用户目录 ~/bin/ 中。在上例中使用名称 hello.sh

  3. 该脚本需要可执行权限。使用以下命令设置权限:

    > chmod +x ~/bin/hello.sh

如果满足上述所有先决条件,则可以采用如下方式执行此脚本:

  1. 作为绝对路径: 可以使用绝对路径执行脚本。在本例中为 ~/bin/hello.sh

  2. 所有位置: 如果 PATH 环境变量包含脚本所在目录,则可以使用 hello.sh 来执行该脚本。

1.3 重定向命令事件

每个命令都可以使用三个通道输入或输出:

  • 标准输出: 这是默认的输出通道。在命令打印某些内容时都会使用标准输出通道。

  • 标准输入: 如果一个命令需要用户或其他命令输入,则使用此通道。

  • 标准错误: 命令使用此通道报告错误。

要重定向这些通道,有以下可行的操作方式:

Command > File

将该命令的输出保存到文件中,会删除现有文件。例如,ls 命令会将其输出写入文件 listing.txt

> ls > listing.txt
Command >> File

将命令输出追加到文件。例如,ls 命令会将其输出追加到文件 listing.txt

> ls >> listing.txt
Command < File

读取该文件作为给定命令的输入。例如,read 命令会将此文件的内容读入变量:

> read a < foo
Command1 | Command2

将左侧命令的输出重定向为右侧命令的输入。例如,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︰ 有用的环境变量

HOME

当前用户的用户主目录

HOST

当前主机名

LANG

当一个工具本地化后,它使用此环境变量中的语言。英语也可以设置为 C

PATH

外壳的搜索路径,冒号分隔的目录列表

PS1

指定在每个命令前打印的普通提示符

PS2

指定在执行多行命令时打印的辅助提示符

PWD

当前工作目录

USER

当前用户

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_1VAR 的内容替代为 PATTERN_2

> file=/home/tux/book/book.tar.bz2
> echo ${file/tux/wilber}
/home/wilber/book/book.tar.bz2

1.6 分组和组合命令

外壳允许您对命令执行连接和分组以有条件地执行。每个命令都返回一个退出码,该退出码确定操作是成功还是失败。如果是 0,则命令成功,任何其他值都表示特定于该命令的一个错误。

以下列表显示可以如何将命令分组:

Command1 ; Command2

顺序地执行这些命令。不检查退出码。以下一行命令使用 cat 显示文件的内容,然后使用 ls 列显其文件属性,而不考虑退出码:

> cat filelist.txt ; ls -l filelist.txt
Command1 && Command2

如果左侧命令成功,则运行右侧命令(逻辑运算符 AND)。仅当上一个命令成功时,以下行才显示文件的内容并打印其文件属性(将其与列表中的上一项相比较):

> cat filelist.txt && ls -l filelist.txt
Command1 || Command2

当左侧命令失败时运行右侧命令(逻辑运算符 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 使用通用流程构造

为了控制脚本的流程,外壳可以使用 forwhileifcase 构造元素。

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

https://bash.cyberciti.biz/guide/If..else..fi 上可以找到更多有用的表达式。

1.7.2 使用 for 命令创建循环

for 循环允许您对一系列项执行命令。例如,以下代码会列显关于当前工作目录中 PNG 文件的特定信息:

for i in *.png; do
 ls -l $i
done

1.8 更多信息

关于 Bash 的重要信息在手册页 man bash 中提供。可以在以下列表中找到关于此主题的更多信息:

Documentation survey