1 Bash入门
本文是一篇对Linux系统最常用的Shell较为全面的总结内容,可以帮助第一次接触Linux系统的新手掌握基础技能,但由于AI如今非常强大,因此请你学会善用AI,本文不会在某些过于具体的内容浪费精力,因为这些你都可以问AI得到答案。
本文更多的只是为你理清楚Bash这个Shell的常见用法,基本运行原理。
Bash 是 Unix 系统和 Linux 系统的一种 Shell(命令行环境),是目前绝大多数 Linux 发行版的默认 Shell。
Shell 这个单词的原意是“外壳”,跟 kernel(内核)相对应,比喻内核外面的一层,即用户跟内核交互的对话界面。
比如我们所熟悉的Windows系统的Shell,指的就是我们经常使用的桌面、文件管理器等等可以让我们用非常简单的方式和系统内核打交道的界面。
但在linux/unix系统中,一般Shell指的是命令行/终端,也就是我们常见到的黑窗口。
它提供了一个与用户对话的环境。在linux/unix环境中只有一个命令提示符,让用户从键盘输入命令,所以又称为命令行环境(command line interface,简写为 CLI)。
Shell 接收到用户输入的命令,将命令送入操作系统执行,并将结果返回给用户。
此外,它还是一个命令解释器,解释用户输入的命令。它支持变量、条件判断、循环操作等语法,所以用户可以用 Shell 命令写出各种小程序,又称为脚本(script)。这些脚本都通过 Shell 的解释执行,而不通过编译。
Shell 有很多种,只要能给用户提供命令行环境的程序,都可以将其看作是 Shell。
历史上,主要的 Shell 有下面这些。
- Bourne Shell(sh)
- Bourne Again shell(bash)
- C Shell(csh)
- TENEX C Shell(tcsh)
- Korn shell(ksh)
- Z Shell(zsh)
- Friendly Interactive Shell(fish)
Bash 是目前最常用的 Shell,下面的命令可以查看当前设备的默认 Shell。
echo $SHELL
如果你没有安装linux/unix环境,那么可以参考文章:Linux系统入门,这篇文章会教你如何在Windows系统中使用WSL体验到linux系统的操作体验。
执行效果如下:
不过要注意的是,有时候当前正在使用的 Shell 不一定就是默认 Shell,一般来说,ps
命令结果的倒数第二行是当前 Shell。
上面示例中,ps
命令结果的倒数第二行显示,运行的命令(cmd
)是bash
,表明当前正在使用的 Shell 是 Bash。
下面的命令可以查看当前的 Linux 系统安装的所有 Shell。
cat /etc/shells
执行效果如下,可以看到出了bash外,还有很多其它类型的shell:
Linux 允许每个用户使用不同的 Shell,用户的默认 Shell 一般都是 Bash,或者与 Bash 兼容。使用chsh
命令(change shell 这两个单词前两个字母的简写),可以改变系统的默认 Shell。
举例来说,要将默认 Shell 从 Bash 改成 sh,从上面的结果中看到sh的路径在/usr/bin/sh
下。
然后,使用chsh
命令切换默认 Shell。
chsh -s /usr/bin/sh
上面命令会将当前的默认 Shell 改成 sh,执行完毕后,重启终端,就会发现此时shell的样式都变了,并且默认shell已经变为sh了:
如果需要改回来,那就再执行一下命令并重启一个终端:
chsh -s /usr/bin/bash
如果是不带有图形环境的 Linux 系统(比如专用于服务器的系统),启动后就直接是命令行环境,比如我这里所使用的wsl便是如此。
不过,现在大部分的 Linux 发行版,尤其是针对普通用户的发行版,都是图形环境。用户登录系统后,自动进入图形环境,需要自己启动终端模拟器,才能进入命令行环境。
所谓“终端模拟器”(terminal emulator)就是一个模拟命令行窗口的程序,让用户在一个窗口中使用命令行环境,并且提供各种附加功能,比如调整颜色、字体大小、行距等等。
不同 Linux 发行版(准确地说是不同的桌面环境)带有的终端程序是不一样的,比如 KDE 桌面环境的终端程序是 konsole,Gnome 桌面环境的终端程序是 gnome-terminal,用户也可以安装第三方的终端程序。所有终端程序,尽管名字不同,基本功能都是一样的,就是让用户可以进入命令行环境,使用 Shell。
进入命令行环境以后,用户会看到 Shell 的提示符。提示符往往是一串前缀,最后以一个美元符号$
结尾,用户可以在这个符号后面输入各种命令。
[user@hostname] $
上面例子中,完整的提示符是[user@hostname] $
,其中前缀是用户名(user
)加上@
,再加主机名(hostname
)。比如,用户名是bill
,主机名是home-machine
,前缀就是bill@home-machine
。
上面我的提示符是:yu@LAPTOP-GQFV3VRC:~$
,从中就能看到我的用户名以及主机了。
注意,根用户(root)的提示符,不以美元符号($
)结尾,而以井号(#
)结尾,用来提醒用户,现在具有根权限,可以执行各种操作,务必小心,不要出现误操作。这个符号是可以自己定义的,这个后续在命令提示符一章节会对其进行介绍。
为了简洁,后文的命令行提示符都只使用$
表示。
一般只要进入命令行环境以后,就已经打开 Bash 了。如果你的 Shell 不是 Bash,可以输入bash
命令启动 Bash。
bash
退出 Bash 环境,可以使用exit
命令,也可以按下Ctrl + d
。
exit
Bash 的基本用法就是在命令行输入各种命令,非常直观。作为练习,可以试着输入pwd
命令。按下回车键,就会显示当前所在的目录。
pwd
如果不小心输入错误,bash还会返回一个提示,表示输入出错,没有对应的可执行程序,并且还会给出一些可能的命令:
用户可以通过bash
命令的--version
参数或者环境变量$BASH_VERSION
,查看本机的 Bash 版本。
bash --version
echo $BASH_VERSION
结果如下:
2 Bash基本语法
Bash的使用,本质上就是其内各种命令的使用,因此这里我们先来认识一下Bash的基本命令。
这并不是Bash独有的,其它Shell基本都是相同的命令,不同Sheel直接大多都是体验不同,Bash之所以受欢迎,一部分原因就是因为它的使用体验比其它Shell更好。
首先最常见的命令是echo,echo
命令的作用是在屏幕输出一行文本,可以将该命令的参数原样输出。
echo hello world
上面例子中,echo
的参数是hello world
,可以原样输出:
如果想要输出的是多行文本,即包括换行符。这时就需要把多行文本放在引号里面。
echo "<HTML>
<HEAD>
<TITLE>Page Title</TITLE>
</HEAD>
<BODY>
Page body.
</BODY>
</HTML>"
上面例子中,echo
可以原样输出多行文本:
默认情况下,echo
输出的文本末尾会有一个回车符。-n
参数可以取消末尾的回车符,使得下一个提示符紧跟在输出内容的后面。
echo -n hello world
效果:
上面例子中,world
后面直接就是下一行的提示符$
。
echo -n a;echo b
上面例子中,-n
参数可以让两个echo
命令的输出连在一起,出现在同一行:
而-e
参数会解释引号(双引号和单引号)里面的特殊字符(比如换行符\n
)。如果不使用-e
参数,即默认情况下,引号会让特殊字符变成普通字符,echo
不解释它们,原样输出。
echo "Hello\nWorld"
echo -e "Hello\nWorld"
执行结果:
上面代码中,-e
参数使得\n
解释为换行符,导致输出内容里面出现换行。
2.1 命令格式
有了上面的echo命令用法,我们就可以来看看命令的通用使用方法。
命令行环境中,主要通过使用 Shell 命令进行各种操作,Shell 命令基本都是下面的格式。
command [ arg1 ... [ argN ]]
上面代码中,command
是具体的命令或者一个可执行文件,arg1 ... argN
是传递给命令的参数,它们是可选的。
ls -l
上面这个命令中,ls
是命令,-l
是参数。
有些参数是命令的配置项,这些配置项一般都以一个连词线开头,比如上面的-l
。
同一个配置项往往有长和短两种形式,比如-l
是短形式,--list
是长形式,它们的作用完全相同。
短形式便于手动输入,长形式一般用在脚本之中,可读性更好,利于解释自身的含义:
# 短形式
ls -r
# 长形式
ls --reverse
上面命令中,-r
是短形式,--reverse
是长形式,作用完全一样,前者便于输入,后者便于理解。
Bash 单个命令一般都是一行,用户按下回车键,就开始执行。有些命令比较长,写成多行会有利于阅读和编辑,这时可以在每一行的结尾加上反斜杠,Bash 就会将下一行跟当前行放在一起解释。
echo foo bar
# 等同于
echo foo \
bar
Bash 使用空格(或 Tab 键)区分不同的参数。
command foo bar
上面命令中,foo
和bar
之间有一个空格,所以 Bash 认为它们是两个参数。
如果参数之间有多个空格,Bash 会自动忽略多余的空格。
上面命令中,a
和test
之间有多个空格,Bash 会忽略多余的空格。
分号(;
)是命令的结束符,使得一行可以放置多个命令,上一个命令执行结束后,再执行第二个命令。
clear; ls
上面例子中,Bash 先执行clear
命令,执行完成后,再执行ls
命令。
注意,使用分号时,第二个命令总是接着第一个命令执行,不管第一个命令执行成功或失败。
除了分号,Bash 还提供两个命令组合符&&
和||
,允许更好地控制多个命令之间的继发关系。
Command1 && Command2
上面命令的意思是,如果Command1
命令运行成功,则继续运行Command2
命令。
Command1 || Command2
上面命令的意思是,如果Command1
命令运行失败,则继续运行Command2
命令。
下面是一些例子。
cat filelist.txt ; ls -l filelist.txt
上面例子中,只要cat
命令执行结束,不管成功或失败,都会继续执行ls
命令。
cat filelist.txt && ls -l filelist.txt
上面例子中,只有cat
命令执行成功,才会继续执行ls
命令。如果cat
执行失败(比如不存在文件flielist.txt
),那么ls
命令就不会执行。
mkdir foo || mkdir bar
上面例子中,只有mkdir foo
命令执行失败(比如foo
目录已经存在),才会继续执行mkdir bar
命令,如果mkdir foo
命令执行成功,就不会创建bar
目录了。
2.2 type
Bash 本身内置了很多命令,同时也可以执行外部程序。怎么知道一个命令是内置命令,还是外部程序呢?
type
命令用来判断命令的来源。
上面代码中,type
命令告诉我们,echo
是内部命令,ls
是别名。
如果要查看一个命令的所有定义,可以使用type
命令的-a
参数。
type -a echo
结果:
上面代码表示,echo
命令既是内置命令,也有对应的外部程序。
type
命令的-t
参数,可以返回一个命令的类型:别名(alias),关键词(keyword),函数(function),内置命令(builtin)和文件(file)。
上面例子中,bash
是文件,ls
是别名,if
是关键词。
2.3 快捷键
Bash 提供很多快捷键,可以大大方便操作。下面是一些最常用的快捷键:
Ctrl + L
:清除屏幕并将当前行移到页面顶部。Ctrl + C
:中止当前正在执行的命令。Shift + PageUp
:向上滚动。Shift + PageDown
:向下滚动。Ctrl + U
:从光标位置删除到行首。Ctrl + K
:从光标位置删除到行尾。Ctrl + W
:删除光标位置前一个单词。Ctrl + D
:关闭 Shell 会话。↑
,↓
:浏览已执行命令的历史记录。
除了上面的快捷键,Bash 还具有自动补全功能。命令输入到一半的时候,可以按下 Tab 键,Bash 会自动完成剩下的部分。比如,输入tou
,然后按一下 Tab 键,Bash 会自动补上ch
。
除了命令的自动补全,Bash 还支持路径的自动补全。有时需要输入很长的路径,这时只需要输入前面的部分,然后按下 Tab 键,就会自动补全后面的部分。如果有多个可能的选择,按两次 Tab 键,Bash 会显示所有选项,让你选择。
3 自定义命令提示符
用户进入 Bash 以后,Bash 会显示一个命令提示符,用来提示用户在该位置后面输入命令。
命令提示符通常是美元符号$
,对于根用户则是井号#
。这个符号是环境变量PS1
决定的,执行下面的命令,可以看到当前命令提示符的定义。
$ echo $PS1
结果如下:
看上去这里提示符定义的很奇怪,因为其中包含了各种类型的符号,而这些符号都有自己的含义,将其一步步拆解后,你就就会发现它最终展示的效果就是我们现在所能看到的:yu@LAPTOP-GQFV3VRC:~$
Bash 允许用户自定义命令提示符,只要改写这个变量即可。改写后的PS1
,可以放在用户的 Bash 配置文件.bashrc
里面,以后新建 Bash 对话时,新的提示符就会生效。
要在当前窗口看到修改后的提示符,可以执行下面的命令。
source ~/.bashrc
命令提示符的定义,可以包含特殊的转义字符,表示特定内容。
\a
:响铃,计算机发出一记声音。\d
:以星期、月、日格式表示当前日期,例如“Mon May 26”。\h
:本机的主机名。\H
:完整的主机名。\j
:运行在当前 Shell 会话的工作数。\l
:当前终端设备名。\n
:一个换行符。\r
:一个回车符。\s
:Shell 的名称。\t
:24小时制的hours:minutes:seconds
格式表示当前时间。\T
:12小时制的当前时间。\@
:12小时制的AM/PM
格式表示当前时间。\A
:24小时制的hours:minutes
表示当前时间。\u
:当前用户名。\v
:Shell 的版本号。\V
:Shell 的版本号和发布号。\w
:当前的工作路径。\W
:当前目录名。\!
:当前命令在命令历史中的编号。\#
:当前 shell 会话中的命令数。\$
:普通用户显示为$
字符,根用户显示为#
字符。\[
:非打印字符序列的开始标志。\]
:非打印字符序列的结束标志。
举例来说,[\u@\h \W]\$
这个提示符定义,显示出来就是[user@host ~]$
(具体的显示内容取决于你的系统)。
改写PS1
变量,就可以改变这个命令提示符,比如。
PS1="\A \h \$ "
此时提示符的样式就已经变了:
注意在修改提示符的时候,$
后面最好跟一个空格,这样的话,用户的输入与提示符就不会连在一起。
由于这里只是在当前打开的shell中修改的,所以下次重启终端就会恢复原样,如果你希望保持这个样式,那么可以修改bash的配置文件,将上面的代码复制到该文件的最后即可:
vim ~/.bashrc
这里的vim是一个非常通用的终端文件编辑器,后面会对它的使用进行介绍。
默认情况下,命令提示符是显示终端预定义的颜色。Bash 允许自定义提示符颜色。
使用下面的代码,可以设定其后文本的颜色。
\033[0;30m
:黑色\033[1;30m
:深灰色\033[0;31m
:红色\033[1;31m
:浅红色\033[0;32m
:绿色\033[1;32m
:浅绿色\033[0;33m
:棕色\033[1;33m
:黄色\033[0;34m
:蓝色\033[1;34m
:浅蓝色\033[0;35m
:粉红\033[1;35m
:浅粉色\033[0;36m
:青色\033[1;36m
:浅青色\033[0;37m
:浅灰色\033[1;37m
:白色
举例来说,如果要将提示符设为红色,可以将PS1
设成下面的代码。
PS1='\[\033[0;31m\]<\u@\h \W>\$'
但是,上面这样设置以后,用户在提示符后面输入的文本也是红色的。为了解决这个问题, 可以在结尾添加另一个特殊代码\[\033[00m\]
,表示将其后的文本恢复到默认颜色。
PS1='\[\033[0;31m\]<\u@\h \W>\$\[\033[00m\]'
除了设置前景颜色,Bash 还允许设置背景颜色。
\033[0;40m
:蓝色\033[1;44m
:黑色\033[0;41m
:红色\033[1;45m
:粉红\033[0;42m
:绿色\033[1;46m
:青色\033[0;43m
:棕色\033[1;47m
:浅灰色
下面是一个带有红色背景的提示符。
PS1='\[\033[0;41m\]<\u@\h \W>\$\[\033[0m\] '
运行后的效果为:
除了PS1
,Bash 还提供了提示符相关的另外三个环境变量。
环境变量PS2
是命令行折行输入时系统的提示符,默认为>
。
echo "hello
> world"
上面命令中,输入hello
以后按下回车键,系统会提示继续输入。这时,第二行显示的提示符就是PS2
定义的>
。
环境变量PS3
是使用select
命令时,系统输入菜单的提示符。
环境变量PS4
默认为+
。它是使用 Bash 的-x
参数执行脚本时,每一行命令在执行前都会先打印出来,并且在行首出现的那个提示符。
比如下面是脚本test.sh
。
#!/bin/bash
echo "hello world"
使用-x
参数执行这个脚本。
$ bash -x test.sh
+ echo 'hello world'
hello world
上面例子中,输出的第一行前面有一个+
,这就是变量PS4
定义的。
这些变量你同样可以修改。
比如我自定义了一个比较实用的命令行样式:
PS1='\n\[\e[34m\]pwd:\w\[\e[0m\]\n\[\e[33m\][\t]\[\e[0m\] \[\e[32m\]\u@\h\[\e[0m\]$ '
效果如下:
这里将当前时间添加进了命令行提示符,并将当前目录提到了前面、避免因路径太长导致命令不好输入、查看。
4 操作历史
Bash 会保留用户的操作历史,即用户输入的每一条命令都会记录,默认是保存最近的500条命令。有了操作历史以后,就可以使用方向键的↑
和↓
,快速浏览上一条和下一条命令。
退出当前 Shell 的时候,Bash 会将用户在当前 Shell 的操作历史写入~/.bash_history
文件,该文件默认储存500个操作。
环境变量HISTFILE
总是指向这个文件。
echo $HISTFILE
直接结果:
history
命令会输出.bash_history
文件的全部内容,即输出操作历史。
history
用户可以使用这个命令,查看最近的操作。
相比直接读取.bash_history
文件,它的优势在于所有命令之前加上了行号。最近的操作在最后面,行号最大:
如果想搜索某个以前执行的命令,可以配合grep
命令搜索操作历史。
history | grep /usr/bin
上面命令返回.bash_history
文件里面,那些包含/usr/bin
的命令,其中grep是过了命令,|
符号是管道,意思是将前面命令的输出作为后面命令的输入。
history
命令的-c
参数可以清除操作历史,即清空.bash_history
文件。
history -c
通过定制环境变量HISTTIMEFORMAT
,history
的输出结果还可以显示每个操作的时间。
HISTTIMEFORMAT='%F %T '
此时执行结果:
上面代码中,%F
相当于%Y - %m - %d
(年-月-日),%T
相当于 %H : %M : %S
(时:分:秒)。
只要设置HISTTIMEFORMAT
这个环境变量,就会在.bash_history
文件保存命令的执行时间戳。如果不设置,就不会保存时间戳。
环境变量HISTSIZE
设置保存历史操作的数量。
HISTSIZE=10000
上面命令设置保存过去10000条操作历史。
如果不希望保存本次操作的历史,可以设置HISTSIZE
等于0。
HISTSIZE=0
如果HISTSIZE=0
写入用户主目录的~/.bashrc
文件,那么就不会保留该用户的操作历史。如果写入/etc/profile
,整个系统都不会保留操作历史。
环境变量HISTIGNORE
可以设置哪些命令不写入操作历史。
HISTIGNORE='pwd:ls:exit'
上面示例设置,pwd
、ls
、exit
这三个命令不写入操作历史。
4.1 执行历史命令
输入命令时,按下Ctrl + r
快捷键,就可以搜索操作历史,选择以前执行过的命令。
Ctrl + r
相当于打开一个.bash_history
文件的搜索接口,直接键入命令的开头部分,Shell 就会自动在该文件中反向查询(即先查询最近的命令),显示最近一条匹配的结果,这时按下回车键,就会执行那条命令。
操作历史的每一条记录都有行号。知道了命令的行号以后,可以用感叹号 + 行号
执行该命令。如果想要执行.bash_history
里面的第8条命令,可以像下面这样操作。
!8
如果想执行本次 Shell 对话中倒数的命令,比如执行倒数第3条命令,就可以输入!-3
。
!-3
上面示例中,!-3
返回倒数第3条命令。
它跟! + 行号
的主要区别是,后者是在.bash_history
文件中从头开始计算行数,而!- 数字
是从底部开始向上计算行数。
!!
命令返回上一条命令。如果需要重复执行某一条命令,就可以不断键入!!
,这样非常方便,它等同于!-1
。
echo hello
!!
上面示例中,!!
会返回并执行上一条命令echo hello
。
有时候,我们使用某条命令,系统报错没有权限,这时就可以使用sudo !!
。
# 报错,没有执行权限
apt update
sudo !!
效果如下:
感叹号 + 搜索词
可以快速执行匹配的命令。
echo Hello World
echo Goodbye
!e
上面例子中,!e
表示找出操作历史之中,最近的那一条以e
开头的命令并执行。Bash 会先输出那一条命令echo Goodbye
,然后直接执行。
同理,!echo
也会执行最近一条以echo
开头的命令。
!echo
效果:
但要注意,感叹号 + 搜索词
语法只会匹配命令,不会匹配参数。所以!echo H
不会执行echo Hello World
,而是会执行echo Goodbye
,并把参数H
附加在这条命令之后。
同理,!echo H G
也是等同于echo Goodbye
命令之后附加H G
。
由于感叹号 + 搜索词
会扩展成以前执行过的命令,所以含有!
的字符串放在双引号里面,必须非常小心,如果它后面有非空格的字符,就很有可能报错。
echo "I say:\"hello!\""
报错:
上面的命令会报错,原因是感叹号后面是一个反斜杠,Bash 会尝试寻找以前是否执行过反斜杠开头的命令,一旦找不到就会报错。解决方法就是在感叹号前面也加上反斜杠。
echo "I say:\"hello\!\""
然后是!? + 搜索词
可以搜索命令的任意部分,包括参数部分。它跟! + 搜索词
的主要区别是,后者是从行首开始匹配。
cat hello.txt
!?hello.txt
上面示例中,!?hello.txt
会返回最近一条包括hello.txt
的命令。
紧接着!$
代表上一个命令的最后一个参数,它的另一种写法是$_
。
而!*
则代表上一个命令的所有参数,即除了命令以外的所有部分。
cp a.txt b.txt
echo !$
cp a.txt b.txt
echo !*
效果如下:
上面示例中,!$
代表上一个命令的最后一个参数(b.txt
),!*
代表上一个命令的所有参数(a.txt b.txt
)。
如果想匹配上一个命令的某个指定位置的参数,可以使用!:n
。
ls a.txt b.txt c.txt
echo !:2
上面示例中,!:2
返回上一条命令的第二个参数(b.txt
)。
这种写法的!:$
,代表上一个命令的最后一个参数。事实上,!$
就是!:$
的简写形式。
ls a.txt b.txt c.txt
echo !:$
上面示例中,!:$
代表上一条命令的最后一个参数(c.txt
)。
如果想匹配更久以前的命令的参数,可以使用!<命令>:n
(指定位置的参数)和!<命令>:$
(最后一个参数)。
ls !mkdir:$
上面示例中,!mkdir:$
会返回前面最后一条mkdir
命令的最后一个参数。
ls !mk:2
上面示例中,!mk:2
会返回前面最后一条以mk
开头的命令的第二个参数。
而如果只是想输出上一条命令,而不是执行它,可以使用!:p
。
echo hello
!:p
上面示例中,!:p
只会输出echo hello
,而不会执行这条命令:
如果想输出最近一条匹配的命令,而不执行它,可以使用!<命令>:p
。
!su:p
上面示例中,!su:p
会输出前面最近一条以su
开头的命令,而不执行它。
另一种语法为^string1^string2
,可以用来执行最近一条包含string1
的命令,将其替换成string2
。
ls a.txt
^txt^log
上面示例中,^error^access
将最近一条含有txt
的命令里面的txt
,替换成log
并执行:
上面的那些快捷命令(比如!!
命令),都是找到匹配的命令后,直接执行。如果希望增加一个确认步骤,先输出是什么命令,让用户确认后再执行,可以打开 Shell 的histverify
选项。
shopt -s histverify
打开histverify
这个选项后,使用!
快捷键所返回的命令,就会先输出,等到用户按下回车键后再执行。
4.2 快捷键
下面是其他一些与操作历史相关的快捷键。
Ctrl + p
:显示上一个命令,与向上箭头效果相同(previous)。Ctrl + n
:显示下一个命令,与向下箭头效果相同(next)。Alt + <
:显示第一个命令。Alt + >
:显示最后一个命令,即当前的命令。Ctrl + o
:执行历史文件里面的当前条目,并自动显示下一条命令。这对重复执行某个序列的命令很有帮助。