查看原文
其他

精通Linux系列三:Linux命令行Shell

拾叁 更AI 2023-10-21

点击关注公众号,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

告诉你有多少用户正在登录。竖线,叫做管道,连接了whowc

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)

每个<()操作符都代表了命令行上的一个文件名,就像那个“文件”包含了lscut的输出。

组合命令

要在单个命令行上顺序调用几个命令,用分号隔开它们:

→ 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向后移动一个字符
^A0转到行首
^E$转到行尾
^Dx删除下一个字符
^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. 1. 按^J来得到一个shell提示符。这个按键产生的字符与Enter键(新行)相同,但即使Enter键不工作,它也会工作。

  2. 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。赚过钱也亏过钱。一路过来,给我最深的感受就是不管学什么,一定要不断学习。只要你能坚持下来,就很容易实现弯道超车!所以,不要问我现在干什么是否来得及。如果你还没什么方向,可以先关注我,这里会经常分享一些前沿资讯和编程知识,帮你积累弯道超车的资本。



您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存