Bash Shell 脚本/简单命令
一个简单命令由以空格或制表符分隔的词语序列组成。第一个词语被认为是命令的名称,其余词语作为参数传递给命令。我们已经看到了许多简单命令的示例;这里有一些更多示例
cd ..
- 此命令使用
cd
(“更改目录”;一个用于在文件系统中导航的内置命令)向上导航一个目录。 - 符号
..
表示“父目录”。例如,/foo/bar/../baz.txt
等价于/foo/baz.txt
。
- 此命令使用
rm foo.txt bar.txt baz.txt
- 假设程序
rm
(“删除”)已安装,此命令将删除当前目录中的文件foo.txt
、bar.txt
和baz.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 -
提示 使用没有参数的 |
要打印你当前所在的路径,可以使用另一个名为pwd
的内置 Unix 实用程序。
我们在上面看到命令rm foo.txt bar.txt baz.txt
删除了三个单独的文件:foo.txt
、bar.txt
和 baz.txt
。这是因为 Bash 根据空格将命令拆分成四个独立的词语,其中三个词语成为程序rm
的参数。但如果我们需要删除一个名称中包含空格的文件怎么办?
注意 在 Unix、GNU/Linux 发行版和其他类似 Unix 的系统中,文件名可以包含空格、制表符、换行符,甚至控制字符。 |
Bash 提供了多种引用机制,这些机制在这种情况下非常有用;最常用的机制是单引号'
和双引号"
。以下两个命令都会删除一个名为this file.txt
的文件
rm 'this file.txt'
rm "this file.txt"
在引号内,空格字符将失去其作为词语分隔符的特殊含义。通常我们将整个词语放在引号中,如上所示,但实际上,实际上只需要将空格本身括起来即可;this' 'file.txt
或 this" "file.txt
等价于'this file.txt'
。
另一种常用的引用机制是反斜杠\
,但它的工作方式略有不同;它只引用(或“转义”)一个字符。因此,此命令等价于上面的命令
rm this\ file.txt
在所有这些情况下,引用字符本身都不会传递给程序。(这称为引用移除。)因此,rm
无法知道它是以何种方式调用的——例如——以rm foo.txt
还是rm 'foo.txt'
的形式调用的。
Bash 支持多种特殊符号,称为扩展,用于将常用的参数类型传递给程序。
其中之一是文件名扩展,其中模式(如*.txt
)将被替换为与该模式匹配的所有文件的名称。例如,如果当前目录包含文件foo.txt
、bar.txt
、this file.txt
和 something.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
。
注意 如果任何文件名以连字符 |
如果我们有一个名为*.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
第一个命令显式地列出每个参数。另外三个命令都使用花括号扩展来更简洁地表达参数:在第二个命令中,给出所有可能性1
到 5
,以逗号分隔;在第三个命令中,给出数字序列(“从 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
且不包含 bar
的 input.txt
行。