Bash脚本入门

Bash 是 Linux 和 Mac 的默认 Shell(命令行环境),系统管理和服务器开发都需要它。

1. Bash 简介

Bash 是 Unix 系统和 Linux 系统的一种 Shell(命令行环境),是目前绝大多数 Linux 发行版的默认 Shell。

1.1 Shell 的含义

学习 Bash,首先需要理解 Shell 是什么。Shell 这个单词的原意是“外壳”,跟 kernel(内核)相对应,比喻内核外面的一层,即用户跟内核交互的对话界面。

具体来说,Shell 这个词有多种含义。

首先,Shell 是一个程序,提供一个与用户对话的环境。这个环境只有一个命令提示符,让用户从键盘输入命令,所以又称为命令行环境(command line interface,简写为 CLI)。Shell 接收到用户输入的命令,将命令送入操作系统执行,并将结果返回给用户。本书中,除非特别指明,Shell 指的就是命令行环境。

其次,Shell 是一个命令解释器,解释用户输入的命令。它支持变量、条件判断、循环操作等语法,所以用户可以用 Shell 命令写出各种小程序,又称为脚本(script)。这些脚本都通过 Shell 的解释执行,而不通过编译。

最后,Shell 是一个工具箱,提供了各种小工具,供用户方便地使用操作系统的功能。

1.2 Shell 的种类

Shell 有很多种,只要能给用户提供命令行环境的程序,都可以看作是 Shell。

历史上,主要的 Shell 有下面这些。

  • Bourne Shell(sh)
  • Bourne Again shell(bash)
  • C Shell(csh)
  • TENEX C Shell(tcsh)
  • Korn shell(ksh)
  • Z Shell(zsh)
  • Friendly Interactive Shell(fish)
  • Bash 是目前最常用的 Shell,除非特别指明,下文的 Shell 和 Bash 当作同义词使用,可以互换。

下面的命令可以查看当前设备的默认 Shell。

1
2
$ echo $SHELL
/bin/bash

当前正在使用的 Shell 不一定是默认 Shell,一般来说,ps命令结果的倒数第二行是当前 Shell。

1
2
3
4
$ ps
    PID TTY          TIME CMD
  21944 pts/4    00:00:00 bash
  40013 pts/4    00:00:00 ps

上面示例中,ps命令结果的倒数第二行显示,运行的命令(cmd)是bash,表明当前正在使用的 Shell 是 Bash。

下面的命令可以查看当前的 Linux 系统安装的所有 Shell。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$ cat /etc/shells 
# /etc/shells: valid login shells
/bin/sh
/bin/bash
/usr/bin/bash
/bin/rbash
/usr/bin/rbash
/bin/dash
/usr/bin/dash
/usr/bin/tmux
/usr/bin/screen

上面三个命令中,$是命令行环境的提示符,用户只需要输入提示符后面的内容。

Linux 允许每个用户使用不同的 Shell,用户的默认 Shell 一般都是 Bash,或者与 Bash 兼容。

1.3 命令行环境

1.3.1 终端模拟器

如果是不带有图形环境的 Linux 系统(比如专用于服务器的系统),启动后就直接是命令行环境。

不过,现在大部分的 Linux 发行版,尤其是针对普通用户的发行版,都是图形环境。用户登录系统后,自动进入图形环境,需要自己启动终端模拟器,才能进入命令行环境。

所谓“终端模拟器”(terminal emulator)就是一个模拟命令行窗口的程序,让用户在一个窗口中使用命令行环境,并且提供各种附加功能,比如调整颜色、字体大小、行距等等。

不同 Linux 发行版(准确地说是不同的桌面环境)带有的终端程序是不一样的,比如 KDE 桌面环境的终端程序是 konsole,Gnome 桌面环境的终端程序是 gnome-terminal,用户也可以安装第三方的终端程序。所有终端程序,尽管名字不同,基本功能都是一样的,就是让用户可以进入命令行环境,使用 Shell。

1.3.2 命令行提示符

进入命令行环境以后,用户会看到 Shell 的提示符。提示符往往是一串前缀,最后以一个美元符号$结尾,用户可以在这个符号后面输入各种命令。

1
[user@hostname] $

上面例子中,完整的提示符是[user@hostname] $,其中前缀是用户名(user)加上@,再加主机名(hostname)。比如,用户名是bill,主机名是home-machine,前缀就是bill@home-machine。

注意,根用户(root)的提示符,不以美元符号($)结尾,而以井号(#)结尾,用来提醒用户,现在具有根权限,可以执行各种操作,务必小心,不要出现误操作。这个符号是可以自己定义的。

为了简洁,后文的命令行提示符都只使用$表示。

1.3.3 进入和退出方法

进入命令行环境以后,一般就已经打开 Bash 了。如果你的 Shell 不是 Bash,可以输入bash命令启动 Bash。

1
$ bash

退出 Bash 环境,可以使用exit命令,也可以同时按下Ctrl + d

1
$ exit

Bash 的基本用法就是在命令行输入各种命令,非常直观。作为练习,可以试着输入pwd命令。按下回车键,就会显示当前所在的目录。

1
2
$ pwd
/home/ubuntu

如果不小心输入了pwe,会返回一个提示,表示输入出错,没有对应的可执行程序。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
$ pwe

Command 'pwe' not found, did you mean:

  command 'pee' from deb moreutils (0.63-1)
  command 'pwd' from deb coreutils (8.30-3ubuntu2)
  command 'we' from deb xwpe (1.5.30a-2.1build3)
  command 'xwe' from deb xwpe (1.5.30a-2.1build3)
  command 'wpe' from deb xwpe (1.5.30a-2.1build3)

Try: sudo apt install <deb name>

1.4 Shell 和 Bash 的历史

Shell 伴随着 Unix 系统的诞生而诞生。

1969年,Ken Thompson 和 Dennis Ritchie 开发了第一版的 Unix。

1971年,Ken Thompson 编写了最初的 Shell,称为 Thompson shell,程序名是sh,方便用户使用 Unix。

1973年至1975年间,John R. Mashey 扩展了最初的 Thompson shell,添加了编程功能,使得 Shell 成为一种编程语言。这个版本的 Shell 称为 Mashey shell。

1976年,Stephen Bourne 结合 Mashey shell 的功能,重写一个新的 Shell,称为 Bourne shell。

1978年,加州大学伯克利分校的 Bill Joy 开发了 C shell,为 Shell 提供 C 语言的语法,程序名是csh。它是第一个真正替代sh的 UNIX shell,被合并到 Berkeley UNIX 的 2BSD 版本中。

1979年,UNIX 第七版发布,内置了 Bourne Shell,导致它成为 Unix 的默认 Shell。注意,Thompson shell、Mashey shell 和 Bourne shell 都是贝尔实验室的产品,程序名都是sh。对于用户来说,它们是同一个东西,只是底层代码不同而已。

1983年,David Korn 开发了Korn shell,程序名是ksh。

1985年,Richard Stallman 成立了自由软件基金会(FSF),由于 Shell 的版权属于贝尔公司,所以他决定写一个自由版权的、使用 GNU 许可证的 Shell 程序,避免 Unix 的版权争议。

1988年,自由软件基金会的第一个付薪程序员 Brian Fox 写了一个 Shell,功能基本上是 Bourne shell 的克隆,叫做 Bourne-Again SHell,简称 Bash,程序名为bash,任何人都可以免费使用。后来,它逐渐成为 Linux 系统的标准 Shell。

1989年,Bash 发布1.0版。

1996年,Bash 发布2.0版。

2004年,Bash 发布3.0版。

2009年,Bash 发布4.0版。

2019年,Bash 发布5.0版。

用户可以通过bash命令的--version参数或者环境变量$BASH_VERSION,查看本机的 Bash 版本。

1
2
3
4
5
6
7
$ bash --version
GNU bash, version 5.0.17(1)-release (x86_64-pc-linux-gnu)
Copyright (C) 2019 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

或者

1
2
$ echo $BASH_VERSION
5.0.17(1)-release

2. Bash 的基本语法

2.1 echo 命令

由于后面的例子会大量用到echo命令,这里先介绍这个命令。

echo命令的作用是在屏幕输出一行文本,可以将该命令的参数原样输出。

1
2
$ echo hello world
hello world

上面例子中,echo的参数是hello world,可以原样输出。

如果想要输出的是多行文本,即包括换行符。这时就需要把多行文本放在引号里面。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
$ echo "<HTML>
>     <HEAD>
>           <TITLE>Page Title</TITLE>
>     </HEAD>
>     <BODY>
>           Page body.
>     </BODY>
> </HTML>"
<HTML>
    <HEAD>
          <TITLE>Page Title</TITLE>
    </HEAD>
    <BODY>
          Page body.
    </BODY>
</HTML>

上面例子中,echo可以原样输出多行文本。

2.1.1 -n参数

默认情况下,echo输出的文本末尾会有一个回车符。-n参数可以取消末尾的回车符,使得下一个提示符紧跟在输出内容的后面。

1
2
$ echo -n hello world
hello world$

上面例子中,world后面直接就是下一行的提示符$。

1
2
3
4
5
6
$ echo a;echo b
a
b

$ echo -n a;echo b
ab

2.1.2 -e参数

-e参数会解释引号(双引号和单引号)里面的特殊字符(比如换行符\n)。如果不使用-e参数,即默认情况下,引号会让特殊字符变成普通字符,echo不解释它们,原样输出。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
$ echo "Hello\nWorld"
Hello\nWorld

# 双引号的情况
$ echo -e "Hello\nWorld"
Hello
World

# 单引号的情况
$ echo -e 'Hello\nWorld'
Hello
World

2.2 命令格式

命令行环境中,主要通过使用 Shell 命令,进行各种操作。Shell 命令基本都是下面的格式。

1
$ command [ arg1 ... [ argN ]]

上面代码中,command是具体的命令或者一个可执行文件,arg1 ... argN是传递给命令的参数,它们是可选的。

1
$ ls -l

上面这个命令中,ls是命令,-l是参数。

有些参数是命令的配置项,这些配置项一般都以一个连词线开头,比如上面的-l。同一个配置项往往有长和短两种形式,比如-l是短形式,--list是长形式,它们的作用完全相同。短形式便于手动输入,长形式一般用在脚本之中,可读性更好,利于解释自身的含义。

1
2
3
4
5
# 短形式
$ ls -r

# 长形式
$ ls --reverse

上面命令中,-r是短形式,--reverse是长形式,作用完全一样。前者便于输入,后者便于理解。

Bash 单个命令一般都是一行,用户按下回车键,就开始执行。有些命令比较长,写成多行会有利于阅读和编辑,这时可以在每一行的结尾加上反斜杠,Bash 就会将下一行跟当前行放在一起解释。

1
2
3
4
5
$ echo foo bar

# 等同于
$ echo foo \
bar

2.3 空格

Bash 使用空格(或 Tab 键)区分不同的参数。

1
$ command foo bar

上面命令中,foobar之间有一个空格,所以 Bash 认为它们是两个参数。

如果参数之间有多个空格,Bash 会自动忽略多余的空格。

1
2
$ echo this is a     test
this is a test

上面命令中,atest之间有多个空格,Bash 会忽略多余的空格。

2.4 分号

分号(;)是命令的结束符,使得一行可以放置多个命令,上一个命令执行结束后,再执行第二个命令。

1
$ clear; ls

上面例子中,Bash 先执行clear命令,执行完成后,再执行ls命令。

注意,使用分号时,第二个命令总是接着第一个命令执行,不管第一个命令执行成功或失败。

2.5 命令的组合符&&||

除了分号,Bash 还提供两个命令组合符&&和||,允许更好地控制多个命令之间的继发关系。

1
Command1 && Command2

上面命令的意思是,如果Command1命令运行成功,则继续运行Command2命令。

1
Command1 || Command2

上面命令的意思是,如果Command1命令运行失败,则继续运行Command2命令。

下面是一些例子。

1
$ cat filelist.txt ; ls -l filelist.txt

上面例子中,只要cat命令执行结束,不管成功或失败,都会继续执行ls命令。

1
$ cat filelist.txt && ls -l filelist.txt

上面例子中,只有cat命令执行成功,才会继续执行ls命令。如果cat执行失败(比如不存在文件flielist.txt),那么ls命令就不会执行。

1
$ mkdir foo || mkdir bar

上面例子中,只有mkdir foo命令执行失败(比如foo目录已经存在),才会继续执行mkdir bar命令。如果mkdir foo命令执行成功,就不会创建bar目录了。

2.6 type 命令

Bash 本身内置了很多命令,同时也可以执行外部程序。怎么知道一个命令是内置命令,还是外部程序呢?

type命令用来判断命令的来源。

1
2
3
4
5
$ type echo
echo is a shell builtin

$ type ls
ls is hashed (/bin/ls)

上面代码中,type命令告诉我们,echo是内部命令,ls是外部程序(/bin/ls)。

type命令本身也是内置命令。

1
2
$ type type
type is a shell builtin

如果要查看一个命令的所有定义,可以使用type命令的-a参数。

1
2
3
4
$ type -a echo
echo is a shell builtin
echo is /usr/bin/echo
echo is /bin/echo

上面代码表示,echo命令既是内置命令,也有对应的外部程序。

type命令的-t参数,可以返回一个命令的类型:别名(alias),关键词(keyword),函数(function),内置命令(builtin)和文件(file)。

1
2
3
4
$ type -t bash
file
$ type -t if
keyword

上面例子中,bash是文件,if是关键词。

2.7 快捷键

Bash 提供很多快捷键,可以大大方便操作。下面是一些最常用的快捷键:

  • Ctrl + L:清除屏幕并将当前行移到页面顶部。
  • Ctrl + C:中止当前正在执行的命令。
  • Shift + PageUp:向上滚动。
  • Shift + PageDown:向下滚动。
  • Ctrl + U:从光标位置删除到行首。
  • Ctrl + K:从光标位置删除到行尾。
  • Ctrl + W:删除光标位置前一个单词。
  • Ctrl + D:关闭 Shell 会话。
  • :浏览已执行命令的历史记录。

除了上面的快捷键,Bash 还具有自动补全功能。命令输入到一半的时候,可以按下 Tab 键,Bash 会自动完成剩下的部分。比如,输入tou,然后按一下 Tab 键,Bash 会自动补上ch

除了命令的自动补全,Bash 还支持路径的自动补全。有时,需要输入很长的路径,这时只需要输入前面的部分,然后按下 Tab 键,就会自动补全后面的部分。如果有多个可能的选择,按两次 Tab 键,Bash 会显示所有选项,让你选择。

3. Bash 的模式扩展

3.1 简介

Shell 接收到用户输入的命令以后,会根据空格将用户的输入,拆分成一个个词元(token)。然后,Shell 会扩展词元里面的特殊字符,扩展完成后才会调用相应的命令。

这种特殊字符的扩展,称为模式扩展(globbing)。其中有些用到通配符,又称为通配符扩展(wildcard expansion)。Bash 一共提供八种扩展。

  • 波浪线扩展
  • ? 字符扩展
  • * 字符扩展
  • 方括号扩展
  • 大括号扩展
  • 变量扩展
  • 子命令扩展
  • 算术扩展

本节介绍这八种扩展。

Bash 是先进行扩展,再执行命令。因此,扩展的结果是由 Bash 负责的,与所要执行的命令无关。命令本身并不存在参数扩展,收到什么参数就原样执行。这一点务必需要记住。

模块扩展的英文单词是globbing,这个词来自于早期的 Unix 系统有一个/etc/glob文件,保存扩展的模板。后来 Bash 内置了这个功能,但是这个名字就保留了下来。

模式扩展与正则表达式的关系是,模式扩展早于正则表达式出现,可以看作是原始的正则表达式。它的功能没有正则那么强大灵活,但是优点是简单和方便。

Bash 允许用户关闭扩展。

1
2
3
$ set -o noglob
# 或者
$ set -f

下面的命令可以重新打开扩展。

1
2
3
$ set +o noglob
# 或者
$ set +f

3.2 波浪线扩展

波浪线~会自动扩展成当前用户的主目录。

1
2
$ echo ~
/home/ubuntu

~/dir表示扩展成主目录的某个子目录,dir是主目录里面的一个子目录名。

0%