Efficient Linux at the Command Line by Daniel J. Barrett (O’Reilly). Copyright 2022 Daniel Barret, 978-1-098-11340-7.

我们可以在命令提示符处输入命令进行操作,那么什么是命令提示符,我们的命令又是如何运行的呢?

命令提示符由计算机壳层 (Shell) 产生,它是存在于我们和Linux系统之间的用户界面。Linux提供多种Shell,最常见的就是bash

bash等Shell可不仅仅是运行命令,例如我们可以使用包含通配符 (*) 的命令一次性指代多个文件:

1
ls *.py
1
data.py  main.py  user_interface.py

通配符是由shell处理的,而非ls程序。

shell也用于处理之前的管道函数,它将stdin与stdout转换,而我们调用的程序并不知道它们是否在互相交流。

文件名的模式匹配 (Pattern Matching or Globbing)

通配符星号 (*) 匹配文件或目录路径中的任何零个或多个字符序列(但不包括开头的点):

1
grep Linux chapter*

在上面的情景中,shell(注意不是grep)将chapter*扩展为一百个匹配的文件名列表,随后shell运行grep

通配符问号 (?) 用于匹配单个字符(但不包括开头的点),例如,要仅匹配chapter1 ~ chapter9,可以使用j:

1
grep Linux chapter?

或者要匹配chapter10 ~ chapter99,可以使用两个问号匹配两位数字:

1
grep Linux chapter??

通配符方括号 ([]) 用于使shell在一个集合中筛选一个字符,例如,要匹配chapter1 ~ chapter5

1
grep Linux chapter[12345]

同理,也可以使用连字符 (-) 用于提供字符的范围:

1
grep Linux chapter[1-5]

结合星号和方括号,我们可以匹配偶数章节:

1
grep Linux chapter*[02468]

当然,并不只有数字可用于方括号中的匹配,例如:

1
ls [A-Z]*_*@

如果一个模式匹配没有任何文件符合,那么其就会原样返回作为命令参数,例如:

1
ls *.doc
1
ls: 无法访问 '*.doc': 没有那个文件或目录

变量评估

运行的shell可以定义并储存变量,例如HOME变量储存量Linux home文件夹的路径,USER变量储存Linux用户名,例如

使用printenv来打印HOME变量和USER变量的值:

1
printenv HOME
1
/home/developer
1
printenv USER
1
developer

在变量名前放置美元符号 ($) ,可以输出变量的值(即evaluating variables):

1
echo My name is $USER and my files are in $HOME
1
My name is developer and my files are in /home/developer

变量的定义

USERHOME等变量都是shell预先定义好的,这些变量在我们登陆时就已经定义好了,按照惯例这些变量是全部大写的。

使用

1
name=value

的语法,我们可以在任何时候定义或者修改一个变量的值。

例如我们可以定义

1
work=$HOME/Projects

注意,定义变量时,等号两边不允许任何空格。

常见误区

当我们使用echo打印变量值时:

1
echo $HOME

我们或许会认为是echo命令将HOME的值输出。但实际上,echo仅仅打印我们赋予的任何参数,是shell完成了变量的评估,再把HOME的值赋给echo

模式匹配 vs 变量

假设我们当前文件夹中有两个子文件夹:mammalsreptiles,而mammals文件夹中含有lizard.txtsnake.txt,我们需要把lizard.txtsnake.txt移动到reptiles文件夹中。

我们有如下两种可能的方法:

方法1:

1
mv mammals/*.txt reptiles

方法2:

1
2
FILES="lizard.txt snake.txt"
mv mammals/$FILES reptiles

显然,方法1是奏效的。

1
echo mammals/*.txt
1
mammals/lizard.txt mammals/snake.txt

因此,方法1相当于输入

1
mv mammals/lizard.txt mammals/snake.txt reptiles

对于我们定义的变量FILES

1
echo $FILES
1
lizard.txt snake.txt

因此,方法1相当于输入

1
mv mammals/lizard.txt snake.txt reptiles

该命令会在当前目录下查找snake.txt并报错:

1
mv mammals/$FILES reptiles
1
mv: 对 'snake.txt' 调用 stat 失败: 没有那个文件或目录

为了使用FILES变量,我们应该使用for循环:

1
2
3
4
FILES="lizard.txt snake.txt"
for f in $FILES; do
mv mammals/$f reptiles
done

使用别名缩短命令

变量代表一个值,shell也用名称代表命令,称为alias,例如:

1
2
alias g=grep
alias ll="ls -l"

我们可以使用已有函数的名称作为别名,这样就在我们的shell中替换了这个函数。

使用不带参数的alias命令可以展示shell中的别名和它们的值:

1
alias
1
2
alias g='grep'
alias ll='ls -l'

使用unalias可以从shell中删除别名:

1
unalias g

输入/输出重定向

shell可以控制其上命令的输入与输出,例如管道 ( | ) ,可以将一个命令的stdout重定向至另一个命令的stdin。

另外一个shell特性是可以将stdout输出至一个文件,例如,将animals.txt中含Perl的行输出至文件,只需在其后添加>,再写上要输出的文件名:

1
grep Perl animals.txt > outfile

如果outfile不存在,将新建一个,若存在,将对其覆写。如果要追加,则应使用>>

与输出重定向相对的是输入重定向,使从文件中获取输入而非stdin,使用<

很多Linux命令不仅接受文件名作为参数,也接受直接的stdin,例如wc命令:

从命名的文件读取:

1
wc animals.txt
1
7  51 325 animals.txt

从重定向的输入读取:

1
wc < animals.txt
1
7  51 325

注意:

一些错误信息 (stderr) ,不能作为stdout使用>重定向。

若要将stderr和stdout都重定向,应使用&<

可以将不同形式的重定向结合起来,例如:

1
2
grep Perl < animals.txt | wc > count
cat count
1
1  6 47

使用引号和转义字符禁用求值

通常,shell将空格作为词语间的分隔符,但如果我们需要shell”认真“对待空格,例如,我们有一文件,名为Efficient Linux Tips.txt,我们就必须使用单引号、双引号或反斜线:

1
2
3
cat 'Efficient Linux Tips.txt'
cat "Efficient Linux Tips.txt"
cat Efficient\ Linux\ Tips.txt

又如,使用

1
echo '$HOME'

即可输出

1
$HOME

在双引号间,反斜线可作为转移字符,但在单引号间则不能。句末的反斜线使原有的换行符失效,从而使命令可以在不同的行间。

在命令别名前的反斜线使shell寻找同名的命令,忽略覆盖:

1
alias less="less -c"

要运行的标准的less命令,可以:

1
\less myfile

定位要运行的程序

我们常见的ls命令其实是硬盘上的一个可执行文件,在/bin中。

1
ls -l /bin/ls
1
-rwxr-xr-x 1 root root 138216  2月  8  2024 /bin/ls

那么shell是如何确定ls/bin中的呢?事实上,shell在一系列预先确定的目录(称为搜索路径,search path)中查找。该列表储存在变量PATH中:

1
echo $PATH
1
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin

搜索路径中目录以:分隔,使用tr命令,将:转变为换行符以获得更清晰的视图:

1
echo $PATH | tr : "\n"
1
2
3
4
5
6
7
8
9
/usr/local/sbin
/usr/local/bin
/usr/sbin
/usr/bin
/sbin
/bin
/usr/games
/usr/local/games
/snap/bin

要在搜索路径中定位一个程序,可以使用which命令:

1
which cp
1
/usr/bin/cp
1
which which
1
/usr/bin/which

搜索路径中可能存在相同名称的命令,shell运行的是在搜索路径中考前的程序。

注意:

shell会先确定命令名称是否已经被设置为别名,再在搜索路径中搜索。

环境和初始化文件

正在运行的shell在变量中保存着大量重要信息:搜索路径、当前目录、偏好的文本编辑器、自定义的shell提示符等。正在运行的shell的变量合称为shell的环境。当shell退出时,其环境会被销毁。

我们可以在初始化文件$HOME/.bashrc中预先定义环境,例如:

1
2
3
4
5
6
7
8
9
10
11
12
# Set the search path
PATH=$HOME/bin:/usr/local/bin:/usr/bin:/bin
# Set the shell prompt
PS1='$ '
# Set your preferred text editor
EDITOR=emacs
# Start in my work directory
cd $HOME/Work/Projects
# Define an alias
alias g=grep
# Offer a hearty greeting
echo "Welcome to Linux, friend!"

\(HOME/.bashrc*中的改变将不影响正在运行的shell,只作用于今后运行的shell。但我们可以强制使正在运行的shell重新读取*\)HOME/.bashrc

1
source $HOME/.bashrc 

1
. $HOME/.bashrc