跳转到内容

Bash Shell 脚本/简单命令

来自维基教科书,开放的书籍,开放的世界

一个简单命令由以空格或制表符分隔的词语序列组成。第一个词语被认为是命令的名称,其余词语作为参数传递给命令。我们已经看到了许多简单命令的示例;这里有一些更多示例

  • cd ..
    • 此命令使用cd(“更改目录”;一个用于在文件系统中导航的内置命令)向上导航一个目录。
    • 符号..表示“父目录”。例如,/foo/bar/../baz.txt 等价于 /foo/baz.txt
  • rm foo.txt bar.txt baz.txt
    • 假设程序rm(“删除”)已安装,此命令将删除当前目录中的文件foo.txtbar.txtbaz.txt
    • Bash 通过搜索可配置的目录列表来查找程序 rm,寻找名为 rm 且可执行的文件(由其文件权限决定)。
  • /foo/bar/baz bip.txt
    • 此命令运行位于/foo/bar/baz 的程序,并将 bip.txt 作为唯一参数传递。
    • /foo/bar/baz 必须可执行(由其文件权限决定)。
    • 警告:请确保正斜杠和后面任何文件之间没有任何空格。 例如,假设“foo”文件夹存在于“根”目录中,那么执行以下命令:“rm -r / foo” 如果在“sudo”访问权限下执行,将会摧毁你的电脑。你已被警告。如果你不理解前面提到的内容,现在不必担心。
    • 如果/foo/bar/baz 是一个文本文件而不是二进制程序,并且它的第一行以#!开头,那么该行剩下的部分将决定用于运行该文件的解释器。例如,如果/foo/bar/baz 的第一行是#!/bin/bash,那么上面的命令等价于/bin/bash /foo/bar/baz bip.txt

使用/foo/bar/baz 的那个示例特别值得注意,因为它说明了如何创建一个像普通程序一样运行的 Bash 脚本:只需将#!/bin/bash 作为脚本的第一行(假设 Bash 位于你系统的那个位置;如果不是,请根据需要调整),并确保脚本具有可读和可执行的正确文件权限。在这本书的剩余部分,所有完整 shell 脚本示例都将以#!/bin/bash 行开头。

正如我们在第一个示例中看到的,实用程序命令cd 用于使用给定参数进行导航。在 Unix 和类似 Unix 的系统(如 GNU/Linux)中,存在两种类型的路径,分别称为相对路径和绝对路径。相对路径相对于你的当前位置,而绝对路径是特定的。以下是一个导航到绝对路径的示例

cd 'path/to/directory'

对于相对路径导航,有一些特定的参数可以实现不同的快捷方式。以下是一些这些参数

要向上导航两个目录,你需要输入

cd ../../

要导航到你之前导航到当前位置的路径,你需要输入

cd -
提示

使用没有参数的cd 直接导航到系统上的默认主目录。

要打印你当前所在的路径,可以使用另一个名为pwd 的内置 Unix 实用程序。

我们在上面看到命令rm foo.txt bar.txt baz.txt 删除了三个单独的文件:foo.txtbar.txtbaz.txt。这是因为 Bash 根据空格将命令拆分成四个独立的词语,其中三个词语成为程序rm 的参数。但如果我们需要删除一个名称中包含空格的文件怎么办?

注意

在 Unix、GNU/Linux 发行版和其他类似 Unix 的系统中,文件名可以包含空格、制表符、换行符,甚至控制字符。

Bash 提供了多种引用机制,这些机制在这种情况下非常有用;最常用的机制是单引号' 和双引号"。以下两个命令都会删除一个名为this file.txt 的文件

rm 'this file.txt'
rm "this file.txt"

在引号内,空格字符将失去其作为词语分隔符的特殊含义。通常我们将整个词语放在引号中,如上所示,但实际上,实际上只需要将空格本身括起来即可;this' 'file.txtthis" "file.txt 等价于'this file.txt'

另一种常用的引用机制是反斜杠\,但它的工作方式略有不同;它只引用(或“转义”)一个字符。因此,此命令等价于上面的命令

rm this\ file.txt

在所有这些情况下,引用字符本身都不会传递给程序。(这称为引用移除。)因此,rm 无法知道它是以何种方式调用的——例如——以rm foo.txt 还是rm 'foo.txt' 的形式调用的。

文件名扩展和波浪号扩展

[编辑 | 编辑源代码]

Bash 支持多种特殊符号,称为扩展,用于将常用的参数类型传递给程序。

其中之一是文件名扩展,其中模式(如*.txt)将被替换为与该模式匹配的所有文件的名称。例如,如果当前目录包含文件foo.txtbar.txtthis file.txtsomething.else,那么此命令

echo *.txt

等价于此命令

echo 'bar.txt' 'foo.txt' 'this file.txt'

这里的星号* 表示“零个或多个字符”;还有其他一些特殊的模式字符(如问号?,表示“正好一个字符”),以及一些其他模式匹配规则,但这种对* 的使用是迄今为止模式的最常见用法。

文件名扩展不一定局限于当前目录中的文件。例如,如果我们想列出/usr/bin 目录中所有与t*.sh 匹配的文件,我们可以这样写

echo /usr/bin/t*.sh

这可能扩展成类似于这样

echo /usr/bin/test.sh /usr/bin/time.sh

如果没有任何文件与指定的模式匹配,则不会进行替换;例如,此命令

echo asfasefasef*avzxv

很可能只会打印asfasefasef*avzxv

注意

如果任何文件名以连字符- 开头,那么文件名扩展有时会产生意想不到的结果。例如,如果一个目录包含两个文件,名为-ntmp.txt,那么cat * 将扩展为cat -n tmp.txtcat 将把-n 解释为选项而不是文件名;相反,最好写cat ./*cat -- *,这将扩展为cat ./-n ./tmp.txtcat -- -n tmp.txt,从而消除此问题。

如果我们有一个名为*.txt 的实际文件,我们想引用它怎么办?(是的,文件名可以包含星号!)那么我们可以使用上面看到的任何一种引用方式,或者使用反斜杠来消除星号的特殊含义。以下两种方式

cat '*.txt'
cat "*.txt"
cat \*.txt

将打印实际文件*.txt,而不是打印所有名称以.txt 结尾的文件。

另一个类似的扩展是波浪号扩展。波浪号扩展有很多功能,但主要功能是:在仅包含波浪号~ 的词语中,或者在以~/(波浪号-斜杠)开头的词语中,波浪号将被替换为当前用户主目录的完整路径。例如,此命令

echo ~/*.txt

将打印当前用户主目录中所有名为*.txt 的文件的名称。

花括号扩展

[编辑 | 编辑源代码]

与文件名扩展类似,花括号扩展是一种简洁的方式来表示多个相似的参数。以下四个命令是等价的

ls file1.txt file2.txt file3.txt file4.txt file5.txt
ls file{1,2,3,4,5}.txt
ls file{1..5..1}.txt
ls file{1..5}.txt

第一个命令显式地列出每个参数。另外三个命令都使用花括号扩展来更简洁地表达参数:在第二个命令中,给出所有可能性15,以逗号分隔;在第三个命令中,给出数字序列(“从 1 到 5,步长为 1”);第四个命令与第三个相同,但将..1 隐式省略。

我们也可以以相反的顺序列出文件

ls file5.txt file4.txt file3.txt file2.txt file1.txt
ls file{5,4,3,2,1}.txt
ls file{5..1..-1}.txt
ls file{5..1}.txt

当序列的终点小于起点时,默认步长为-1

由于在 Bash 中,命令的第一个词语是运行的程序,所以我们也可以这样写命令

{ls,file{1..5}.txt}

但显然这不利于可读性。(顺便说一下,文件名扩展也可以做到类似的事情。)

大括号扩展,类似于文件名扩展,可以通过任何引号机制来禁用;'{'"{"\{ 会生成一个实际的字面大括号。

重定向输出

[编辑 | 编辑源代码]

Bash 允许将命令的标准输出(文件描述符 1)发送到文件,而不是发送到控制台。例如,常见的实用程序 cat 将文件写入标准输出;如果我们将它的标准输出重定向到文件,那么我们就可以将一个文件的内容复制到另一个文件。

如果我们想用命令的输出覆盖目标文件,我们使用这种表示法

cat input.txt > output.txt

如果我们想保留目标文件的现有内容,只将命令的输出追加到末尾,我们使用这种表示法

cat input.txt >> output.txt

然而,并非程序写入控制台的所有内容都通过标准输出。许多程序使用标准错误(文件描述符 2)来显示错误消息和某些类型的“日志记录”或“侧信道”消息。如果我们希望标准错误与标准输出合并,我们可以使用以下两种表示法中的任何一种

cat input.txt &>> output.txt
cat input.txt >> output.txt 2>&1

如果我们希望将标准错误追加到与标准输出不同的文件,我们使用这种表示法

cat input.txt >> output.txt 2>> error.txt

事实上,我们可以只重定向标准错误,而保留标准输出

cat input.txt 2>> error.txt

在以上所有示例中,如果我们想覆盖重定向目标而不是追加到它,我们可以用 > 替换 >>

稍后我们将看到一些更高级的关于输出重定向的操作。

重定向输入

[编辑 | 编辑源代码]

就像 Bash 允许将程序的输出发送到文件一样,它也允许从文件获取程序的输入。例如,常见的 Unix 实用程序 cat 将其输入复制到其输出,这样这个命令

cat < input.txt

会将 input.txt 的内容写入控制台。

正如我们已经看到的,在这种情况下,不需要这个技巧,因为 cat 可以被指示将指定的文件复制到它的输出;上面的命令只是等效于下面的命令

cat input.txt

这是规则而不是例外;大多数常见的 Unix 实用程序,它们可以从控制台获取输入,也具有从文件获取输入的内置功能。事实上,许多程序(包括 cat)可以从多个文件获取输入,使它们比上面更灵活。以下命令将打印出 input1.txt,然后是 input2.txt

cat input1.txt input2.txt

尽管如此,输入重定向有其用途,我们将在后面看到其中一些用途。

管道预览

[编辑 | 编辑源代码]

虽然它们超出了本章的范围,但我们现在已经有了对管道进行初步了解所需的背景知识。管道是一系列由管道字符 | 分隔的命令。每个命令同时运行,每个命令的输出用作下一个命令的输入。

例如,考虑以下管道

cat input.txt | grep foo | grep -v bar

我们已经看到了 cat 实用程序;cat input.txt 只是将文件 input.txt 写入其标准输出。程序 grep 是一个常见的 Unix 实用程序,它根据模式进行过滤(在 Unix 行话中称为“grep”);例如,命令 grep foo 会将包含字符串 foo 的所有输入行打印到其标准输出。命令 grep -v bar 使用 -v 选项反转模式;该命令打印所有包含字符串 bar 的输入行。由于每个命令的输入都是前一个命令的输出,因此最终结果是该管道打印所有包含 foo包含 barinput.txt 行。

华夏公益教科书