精通Linux系列三:Linux命令行Shell
点击关注公众号,AI,编程干货及时送达
为了在Linux系统上运行命令,你需要一个地方来输入它们。这个“地方”被称为shell,这是Linux的命令行用户界面:你输入一个命令,按回车,然后shell就会运行你请求的程序。
例如,要查看谁在电脑上登录了,你可以在shell中执行这个命令:
→ who
silver :0 Sep 23 20:44
byrnes pts/0 Sep 15 13:51
barrett pts/1 Sep 22 21:15
silver pts/2 Sep 22 21:18
一个命令也可以同时调用多个程序,甚至可以将程序连接在一起,使它们互相交互。这里有一个命令,它将who
程序的输出重定向为wc
程序的输入,wc
程序可以计算文件中的文本行数;结果就是who
的输出中的行数:
→ who | wc -l
4
告诉你有多少用户正在登录。竖线,叫做管道,连接了who
和wc
。
shell实际上也是一个程序,而且Linux有好几种。我们主要关注的是bash(Bourne-Again Shell),位于*/bin/bash*,通常是Linux发行版的默认选项。要查看你是否正在运行bash,输入:
→ echo $SHELL
/bin/bash
Shell与程序的区别
当你运行一个命令时,它可能会调用一个Linux程序(像who
),或者可能是一个内置命令,是shell本身的一个功能。你可以使用type
命令来区分:
→ type who
who is /usr/bin/who
→ type cd
cd is a shell builtin
bash Shell的一些特性
shell不仅仅是运行命令。它还具有强大的功能来使这个任务更容易:用于匹配文件名的通配符,“命令历史”用于快速回调之前的命令,管道用于将一个命令的输出变为另一个命令的输入,变量用于存储shell使用的值,等等。花时间学习这些功能,你会在Linux上变得更快、更高效。让我们浅尝辄止的为你介绍这些有用的工具。(要查看完整的文档,运行info bash
。)
通配符
通配符是一种用于表示具有相似名称的文件集合的简写。例如,a*
表示所有以小写字母“a”开头的文件。通配符由shell“展开”为实际匹配的文件名集合。所以,如果你输入:
→ ls a*
shell首先将a*
展开为你当前目录中以“a”开头的文件名,就像你输入的那样:
→ ls aardvark adamantium apple
ls
从来不知道你使用了通配符:它只看到shell展开通配符后的最终文件名列表。这意味着每一个Linux命令,无论其来源,都可以与通配符和其他shell特性一起使用。这一点非常重要。许多Linux用户错误地认为程序会扩展自己的通配符。但事实并非如此。通配符完全由shell在关联的程序运行之前处理。
点文件
以点开始的文件名,在Linux中被称为点文件,是特殊的。当你以一个点开始命名文件时,它将不会被一些程序显示:
•
ls
会从目录列表中省略这个文件,除非你提供-a
选项。• shell的通配符不会匹配开始的点。
实际上,除非你明确要求查看它们,否则点文件是隐藏的。因此,有时它们被称为“隐藏文件”。
通配符永远不会匹配两个字符:一个开始的点,和目录斜杠(/
)。这些必须按字面给出,比如.pro*
来匹配*.profile,或者/etc/*conf
来匹配/etc目录中以conf*结束的所有文件名。
通配符 | 含义 |
* | 零个或多个连续的字符 |
? | 任意单个字符 |
[* set*] | 给定*set *中的任何单个字符,最常见的是一串字符,如[aeiouAEIOU] 表示所有的元音,或者带破折号的范围,如[A-Z] 表示所有的大写字母 |
[^* set*] | 不在给定*set *中的任何单个字符,如[^0-9] 表示任何非数字字符 |
[!* set*] | 与[^* set*] 相同 |
当使用字符集时,如果你想在集合中包含一个字面上的破折号,把它放在首位或者末位。要在集合中包含一个字面上的关闭方括号,把它放在首位。要字面上包含^
或!
符号,不要把它放在首位。
花括号扩展
与通配符类似,带有花括号的表达式也会扩展成命令的多个参数。逗号分隔的表达式:
{X,YY,ZZZ}
首先扩展为X,然后是YY,最后是ZZZ,在命令行中,就像这样:
→ echo sand{X,YY,ZZZ}wich
sandXwich sandYYwich sandZZZwich
花括号适用于任何字符串,不像通配符,它们只有在匹配到现有文件名时才会扩展。
Shell变量
你可以通过赋值来定义变量及其值:
→ MYVAR=3
要引用一个值,只需在变量名前加一个美元符号:
→ echo $MYVAR
3
一些变量是系统的,通常在你登录时由shell定义:
变量 | 含义 |
DISPLAY | 你的X窗口显示名 |
HOME | 你的主目录,例如 /home/smith |
LOGNAME | 你的登录名,例如 smith |
MAIL | 你的收件邮箱,例如 /var/spool/mail/smith |
OLDPWD | 你的shell在最后一个cd 命令之前的目录 |
PATH | 你的shell搜索路径:由冒号分隔的目录 |
PWD | 你的shell的当前目录 |
SHELL | 你的shell路径(例如,*/bin/bash*) |
TERM | 你的终端类型(例如,xterm或vt100) |
USER | 你的登录名 |
变量的作用范围(即,哪些程序知道它)默认是在定义它的shell中。要使变量及其值可用于shell调用的其他程序(即,子shell),使用export
命令:
→ export MYVAR
或者简写形式:
→ export MYVAR=3
你的变量现在被称为环境变量,因为它对你的shell的“环境”中的其他程序可用。所以在前面的例子中,导出的变量MYVAR
对由同一个shell运行的所有程序(包括shell脚本:参见[“Variables”])都可用。
要列出shell的环境变量,运行:
→ printenv
要为特定程序提供环境变量的值一次,将*variable=value
*添加到命令行前面:
→ printenv HOME
/home/smith
→ HOME=/home/sally printenv HOME
/home/sally
→ printenv HOME
/home/smith 原始值不受影响
搜索路径
程序散布在Linux文件系统的各处,比如*/bin和/usr/bin*这样的目录。当你通过shell命令运行程序时,shell是如何找到它的?关键的变量PATH
告诉shell在哪里查找。当你输入任何命令:
→ who
shell通过搜索Linux目录来找到who
程序。shell查阅PATH
的值,这是由冒号分隔的一系列目录:
→ echo $PATH
/usr/local/bin:/bin:/usr/bin
并在这些目录中查找who
命令。如果它找到了who
(比如,*/usr/bin/who*),它就运行该命令。否则,它会报告一个错误,比如:
bash: who: command not found
要暂时向你的shell的搜索路径添加目录,可以修改它的PATH
变量。例如,要将*/usr/sbin*添加到你的shell的搜索路径中:
→ PATH=$PATH:/usr/sbin
→ echo $PATH
/usr/local/bin:/bin:/usr/bin:/usr/sbin
这个改变只影响当前的shell。要使它永久有效,可以在你的启动文件*/.bash_profile中修改PATH
变量。然后注销并重新登录,或者在你的所有打开的shell窗口中手动运行你的/.bash_profile*启动文件:
→ . $HOME/.bash_profile
别名
内建命令alias
可以定义一个方便的简写来替代长命令,以节省打字。比如:
→ alias ll='ls -lG'
定义了一个新命令ll
,用来运行ls -lG
:
→ ll
总计 436
-rw-r--r-- 1 smith 3584 Oct 11 14:59 file1
-rwxr-xr-x 1 smith 72 Aug 6 23:04 file2
...
在你的 ~/.bash_aliases 文件中定义别名,就可以在你每次登录时使用了。输入alias
可以查看你所有的别名。
输入/输出重定向
Shell可以将标准输入、标准输出和标准错误重定向到文件(参见["输入和输出"])。换句话说,任何从标准输入读取的命令都可以使用Shell的 < 操作符将输入来自文件:
→ any command < infile
同样,任何写到标准输出的命令都可以改为写到文件:
→ any command > outfile 创建/覆盖 outfile
→ any command >> outfile 追加到 outfile
写到标准错误的命令也可以将其输出重定向到文件,而标准输出仍然显示在屏幕上:
→ any command 2> errorfile
要将标准输出和标准错误都重定向到文件:
→ any command > outfile 2> errorfile 分别到两个文件
→ any command >& outfile 同一个文件
→ any command &> outfile 同一个文件
管道
你可以用Shell的管道(|)操作符将一个命令的标准输出重定向为另一个命令的标准输入。(在美国键盘上,你可以在Enter键上方找到这个符号。)比如:
→ who | sort
将who
的输出发送到sort
程序,打印一个按字母顺序排序的已登录用户列表。多个管道也可以工作。在这里,我们再次排序who
的输出,提取第一列的信息(使用awk
),并一页一页地显示结果(使用less
):
→ who | sort | awk '{print $1}' | less
进程替换
管道让你可以将一个程序的输出发送给另一个程序。一个更高级的特性,进程替换,让该输出可以伪装成一个命名文件。考虑一个比较两个文件内容的程序。用进程替换操作符,<()
,你可以比较两个程序的输出。
假设你有一个文件夹装满了成对的JPEG和文本文件:
→ ls jpegexample
file1.jpg file1. file2.jpg file2. ...
你想确认每个JPEG文件是否都有对应的文本文件,反之亦然。通常,你可能会创建两个临时文件,一个包含JPEG文件名,另一个包含文本文件名,用cut
移除文件扩展名,然后用diff
比较两个临时文件:
→ cd jpegexample
→ ls *.jpg | cut -d. -f1 > /tmp/jpegs
→ ls *. | cut -d. -f1 > /tmp/texts
→ diff /tmp/jpegs /tmp/texts
5a6
> file6 没有找到file6.jpg
8d8
< file9 没有找到file9.
有了进程替换,你可以用一条命令完成同样的任务,而不需要临时文件:
→ diff <(ls *.jpg|cut -d. -f1) <(ls *.|cut -d. -f1)
每个<()
操作符都代表了命令行上的一个文件名,就像那个“文件”包含了ls
和cut
的输出。
组合命令
要在单个命令行上顺序调用几个命令,用分号隔开它们:
→ command1 ; command2 ; command3
要运行一连串的命令,如果有任何一个失败就停止执行,用&&
(“和”)符号隔开它们:
→ command1 && command2 && command3
要运行一连串的命令,只要有一个成功就停止执行,用||
(“或”)符号隔开它们:
→ command1 || command2 || command3
引用
通常情况下,Shell把空格简单地视为命令行上单词的分隔符。如果你想让一个单词包含空格(例如,一个带空格的文件名),就用单引号或双引号把它括起来,这样Shell就会把它当成一个整体。单引号会原样处理它们的内容,而双引号则让Shell结构(例如变量)可以被求值:
→ echo '变量HOME的值是 $HOME'
变量HOME的值是 $HOME
→ echo "变量HOME的值是 $HOME"
变量HOME的值是 /home/smith
反引号(``)会使它们的内容被视为Shell命令进行求值。内容会被替换为命令的标准输出:
→ date +%Y 打印当前年份
2023
→ echo 今年是 `date +%Y`
今年是 2023
美元符号和括号等同于反引号:
→ echo 今年是 $(date +%Y)
今年是 2023
但是它们优于反引号,因为它们可以被嵌套使用:
→ echo 明年是 $(expr $(date +%Y) + 1)
明年是 2024
转义
如果一个字符对于Shell来说具有特殊含义,但你希望它被原样使用(例如,*
作为实际的星号而不是通配符),那么就在该字符前加上反斜线“\”字符。这就叫做转义特殊字符:
→ echo a* 作为通配符,匹配“a”开头的文件名
aardvark adamantium apple
→ echo a\* 作为实际的星号
a*
→ echo "我住在 $HOME" 打印变量的值
我住在 /home/smith
→ echo "我住在 \$HOME" 一个实际的美元符号
我住在 $HOME
你也可以转义控制字符(制表符、换行符、^D等),以便它们在命令行中被原样使用,如果你在它们前加上^V
。这对于制表符尤其有用,因为Shell否则会用它们进行文件名补全。
→ echo "这里和这里之间有一个制表符^V "
这里和这里之间有一个制表符
命令行编辑
Bash允许你编辑你正在操作的命令行,使用的快捷键受到了文本编辑器emacs和vi的启发(参见[“文件创建和编辑”])。要启用emacs键的命令行编辑,运行此命令(并把它放在你的*~/.bash_profile*中使其永久生效):
→ set -o emacs
对于vi(或vim)键:
→ set -o vi
emacs键盘操作 | vi键盘操作 (按ESC后) | 意义 |
^P 或 上箭头 | k | 转到前一条命令 |
^N 或 下箭头 | j | 转到下一条命令 |
^R | 交互式地搜索前一条命令 | |
^F 或 右箭头 | l | 向前移动一个字符 |
^B 或 左箭头 | h | 向后移动一个字符 |
^A | 0 | 转到行首 |
^E | $ | 转到行尾 |
^D | x | 删除下一个字符 |
^U | ^U | 清除整行 |
命令历史
你可以回顾你之前运行过的命令,即Shell的历史记录,并重新执行它们。一些有用的与历史记录相关的命令如下:
命令 | 含义 |
history | 打印你的历史记录 |
history N | 打印你历史记录中最近的 N 条命令 |
history -c | 清除(删除)你的历史记录 |
!! | 重新执行上一条命令 |
! N | 重新执行你历史记录中编号为 N 的命令 |
!- N | 重新执行你输入的 N 条命令前的命令 |
!$ | 代表上一条命令的最后一个参数;在执行可能会损坏操作的命令之前,检查文件是否存在,例如删除它们:→ ** ls z*** zebra. zookeeper → ** rm !$** 等同于 "rm z*" |
!* | 代表上一条命令的所有参数:→ ** ls myfile emptyfile hugefile** emptyfile hugefile myfile → ** wc !*** 18 211 1168 myfile 0 0 0 emptyfile 333563 2737539 18577838 hugefile 333581 2737750 18579006 total |
文件名补全
在输入文件名的过程中,按下Tab键,Shell会自动为你补全(完成输入)文件名。如果有几个文件名符合你到目前为止输入的内容,Shell会发出蜂鸣声,表明匹配是模糊的。立即再按Tab键,Shell会提供选择项。试试看:
→ cd /usr/bin
→ ls un<Tab><Tab>
Shell将显示 /usr/bin中所有以 un 开头的文件,如 uniq 和 unzip。输入几个更多的字符以消除你的选择的模糊性,然后再按Tab键。
Shell job控制
jobs | 列出你的作业. |
& | 在后台运行一个作业. |
^Z | 暂停当前的(前台)作业. |
suspend | 暂停一个Shell. |
fg | 恢复一个作业:将它带到前台. |
bg | 让一个被暂停的作业在后台运行. |
所有Linux shell都有作业控制功能:能够在后台(在幕后进行多任务处理)和前台(作为你Shell提示符的活动进程运行)运行命令的能力。一个作业就是Shell的工作单位。当你交互式地运行一个命令时,你当前的Shell将它视为一个作业。当命令完成时,相关联的作业就消失了。作业比Linux进程的级别更高;Linux操作系统对它们一无所知。它们只是Shell的构造。以下是关于作业控制的一些重要词汇:
• 前台作业在Shell中运行,占用Shell提示符,所以你不能运行另一个命令
• 后台作业在Shell中运行,但不占用Shell提示符,所以你可以在同一个Shell中运行另一个命令
• 暂停临时停止一个前台作业
• 恢复使一个被暂停的作业再次在前台运行
jobs
内置命令 jobs
列出你当前Shell中运行的作业:
→ jobs
[1]- 正在运行 emacs myfile &
[2]+ 已停止 ssh example.com
左边的整数是作业号,加号标识默认受 fg
(前台)和 bg
(后台)命令影响的作业。
&
放在命令行的末尾,&
让给定的命令作为一个后台作业运行:
→ emacs myfile &
[2] 28090
Shell的回应包括作业号(2)和命令的进程ID(28090)。
^Z
在Shell中输入 ^Z
,当一个作业在前台运行时,将暂停该作业。它只是停止运行,但它的状态被记住了:
→ sleep 10 等待10秒
^Z
[1]+ 已停止 sleep 10
→
现在你已经准备好输入 bg
把命令放到后台,或者 fg
在前台恢复它。你也可以让它暂停并运行其他命令。
suspend
内置命令 suspend
会尽可能暂停当前的Shell,就像你对Shell本身输入了 ^Z
。例如,如果你用 sudo
命令创建了一个超级用户Shell,并想要返回到你原来的Shell:
→ whoami
smith
→ sudo bash
Password: *******# whoami
root
# suspend
[1]+ 已停止 sudo bash
→ whoami
smith
bg
bg [%jobid]
内置命令 bg
将一个被暂停的作业发送到后台运行。如果没有参数,bg
操作最近被暂停的作业。要指定特定的作业(由 jobs
命令显示),提供一个百分号前面的作业号:
→ bg %2
有些类型的交互式作业不能保持在后台运行,例如,如果它们正在等待输入。如果你试图这样做,Shell将暂停作业并显示:
[2]+ 已停止 命令行在此
现在你可以恢复作业(使用 fg
)并继续。
fg
fg [%jobid]
内置命令 fg
将一个被暂停或后台运行的作业带到前台。如果没有参数,它选择一个作业,通常是最近被暂停或后台运行的一个。要指定特定的作业(如 jobs
命令所示),提供一个百分号前面的作业号:
→ fg %2
同时运行多个Shell
job允许你同时管理多个命令,但是一次只能有一个在前台运行。更强大的是,你也可以同时运行多个shell,每个shell都有一个前台命令和任意数量的后台命令。
如果你的Linux计算机运行的是如KDE或Gnome这样的窗口系统,你可以通过打开多个shell窗口同时轻松运行多个shell。此外,某些shell窗口程序,如KDE的konsole
,可以在一个窗口中打开多个标签页,每个标签页都运行一个shell。
即使没有窗口系统——比如,通过SSH网络连接——你也可以同时管理多个shell。screen
程序使用普通的ASCII终端来模拟多个窗口,每个窗口都运行一个shell。使用特殊的按键,你可以随意从一个模拟窗口切换到另一个。(tmux
就是另一个这样的程序。)要开始一个screen
会话,只需运行:
→ screen
你可能会看到一些介绍性的信息,然后就是你的普通shell提示符。看起来好像什么都没发生,但是你现在已经在一个虚拟的“窗口”内运行了一个新的shell。screen
程序提供了10个这样的窗口,标签从0到9。
输入一个简单的命令,如ls
,然后按^A^C
(control-A,control-C)。屏幕会清空,并显示一个新的shell提示符。你实际上是在查看第二个独立的“窗口”。运行一个不同的命令(比如df
),然后按^A^A
,你就会切换回第一个窗口,在那里你的ls
输出现在再次可见。再次按^A^A
就可以切换回第二个窗口。以下是一些常用的screen
按键(查看manpage或键入^A?
获取屏幕帮助):
^A? | 帮助:显示所有按键命令。 |
^A^C | 创建一个窗口。 |
^A0 , ^A1 ... ^A9 | 切换到窗口0到9,依次类推。 |
^A' | 提示输入一个窗口编号(0-9),然后切换到它。 |
^A^N | 数字上切换到下一个窗口。 |
^A^P | 数字上切换到前一个窗口。 |
^A^A | 切换到最近使用过的另一个窗口(在两个窗口之间切换)。 |
^A^W | 列出所有你的窗口。 |
^AN | 显示当前窗口编号。(注意,N 是大写的。) |
^Aa | 向你的shell发送一个真正的control-A,被screen 忽略。在bash中,control-A通常将光标移动到命令行的开始。(注意,第二个a 是小写的。) |
^D | 结束当前的shell。这是普通的“文件结束”按键,解释在[“终止一个Shell”],可以关闭任何shell。 |
^A\ | 杀掉所有窗口并终止screen 。 |
在screen
窗口中运行文本编辑器时要小心。screen
会捕获所有你的control-A按键,即使它们是作为编辑命令的。键入^Aa
将真正的control-A发送给你的应用程序。
终止正在进行的命令
如果你从前台运行的shell启动了一个命令,并想立即终止它,可以输入^C
。shell会识别^C
为“立即终止当前前台命令”。所以,如果你正在显示一个非常长的文件(比如,使用cat
命令),并想停止,输入^C
:
→ cat hugefile
Lorem ipsum dolor sit amet, consectetur adipiscing
odio. Praesent libero. Sed cursus ante dapibus diam.
quis sem at nibh elementum blah blah blah ^C
→
要终止在后台运行的程序,你可以用fg
将其带到前台,然后输入^C
:
→ sleep 50 &
[1] 12752
→ jobs
[1]- Running sleep 50 &
→ fg %1
sleep 50
^C
→
或者,你也可以使用kill
命令(参见[“控制进程”])。
在被杀掉后的程序
用^C
杀掉前台程序可能会让你的shell处于奇怪或无响应的状态,可能不会显示你输入的按键。这是因为被杀掉的程序没有机会清理自己的环境。如果这种情况发生,你可以:
1. 按
^J
来得到一个shell提示符。这个按键产生的字符与Enter键(新行)相同,但即使Enter键不工作,它也会工作。2. 输入shell命令
reset
(即使你输入时字母不显示),然后再次按^J
来运行这个命令。这应该可以将你的shell恢复正常。
输入^C
并不是一个友好的结束程序的方式。如果程序有自己的退出方式,尽可能使用那个(参见前面的侧边栏获取详细信息)。
^C
只在shell中起作用。如果在非shell窗口的应用程序中输入,可能不会有任何效果。此外,一些命令行程序被编写成“捕获”^C
并忽略它:一个例子是文本编辑器emacs。
终止一个Shell
要终止一个shell,可以运行exit
命令或者输入^D
。
→ exit
定制Shell行为
要配置所有的shell以特定的方式工作,编辑你的家目录中的*.bash_profile* 和 .bashrc 文件。每次你登录(~/.bash_profile)或打开一个shell(~/.bashrc)时,这些文件都会执行。它们可以设置变量和别名,运行程序,打印你的星座,或者你喜欢的任何事情。
推荐阅读
你好,我是拾叁,7年开发老司机、互联网两年外企5年。怼得过阿三老美,也被PR comments搞崩溃过。这些年我打过工,创过业,接过私活,也混过upwork。赚过钱也亏过钱。一路过来,给我最深的感受就是不管学什么,一定要不断学习。只要你能坚持下来,就很容易实现弯道超车!所以,不要问我现在干什么是否来得及。如果你还没什么方向,可以先关注我,这里会经常分享一些前沿资讯和编程知识,帮你积累弯道超车的资本。