1、Shell相关概念
在Linux中,Shell这个概念可能指代以下几种情况:
一、命令解释器(The Interpreter):
在Linux/Unix系统中,Shell是用户与Linux内核之间的命令解释器,它接收用户输入的命令,解析后传递给内核执行,并将结果返回给用户。
具体来说,Shell是一个用 C 语言写出来的真实的可执行程序(比如 /bin/bash 或 /bin/zsh)。Linux 的内核(Kernel)只懂机器语言(0和1)。Shell 接收输入的命令(比如 ls),把它翻译成内核能听懂的系统调用去执行,然后再把将命令执行的结果返回并最终显示在屏幕上。
二、系统的“外壳” (The System Shell)
用户 → Shell → Kernel → 硬件
Shell不是内核中的一个模块,而是运行在内核之上。如果不只是指Linux,而是从所有操作系统的宽泛的角度看,在内核外,所有与用户交互的程序集合,包括命令行界面和图形界面,统称为“壳”(Shell)。

三、Shell脚本 (Shell Scripting Language)
一种解释型语言,即通过解释器一边读代码一边执行,方便修改调试。其他常见的解释型语言有Python、JavaScript、Ruby、PHP等。
而另一种编译型语言运行前必须“编译一次”,先整体翻译成机器代码再运行。编译后的机器码直接由CPU执行,速度快。其他常见的编译型语言有C、C++、Go、Rust等。
四、错误的概念:"shell终端"
在Linux中,Shell和终端有关联,但完全不是一回事。当打开一个终端程序时,它会启动一个shell进程(如Bash),用户在终端中输入的命令实际上是由这个shell进程解释执行的。有以下几种常见的容易使人混淆困惑的表述:
"shell终端" :shell不是终端的一部分,而是终端中运行的程序
"在终端中执行shell命令" :准确来说是在终端中运行命令,这些命令由shell解释执行。终端本身无法执行命令,它只是运行shell的环境
"终端是命令行界面":命令行界面是shell提供的功能,不是终端本身,终端是运行命令行界面的程序
登录/非登录Shell、当前Shell、子Shell
这里的Shell指的都是命令解释器
是否为登录Shell主要看密码:输入了密码进来的就是登录 Shell,直接弹出来的就是非登录 Shell。它们的区别在于读取哪个配置文件。
输入
echo $0如果输出的是-bash说明是登录shell,如果输出的是bash则说明是非登录Shell
登录 Shell:加载了完整环境配置文件/初始化环境的 Shell
图形化界面/字符界面输入密码登录
通过 SSH 远程连接到服务器(输入了密码)。
按 Ctrl+Alt+F3 切到纯黑底白字的物理终端,输入账号密码登录。
输入
su - 用户名彻底切换身份,sudo -i切换root身份登录Shell触发的变量文件(触发顺序由上至下):
/etc/bashrc
/etc/profile
~username/.bashrc
~username/.bash_profile
非登录Shell:复用了父进程环境变量/局部初始化的 Shell
在图形桌面里双击打开了一个“终端”窗口。
在终端里敲了一个 bash 命令,又开了一个 Shell
输入
su 用户名切换用户,sudo执行命令(不论是否需要密码)非登录Shell触发的变量文件:
/etc/bashrc
~username/.bashrc
其中,/etc/bashrc和/etc/profile 是全局变量文件,而~username/.bashrc 和
~username/.bash_profile 是用户的变量文件
通过两者对比可以得出结论:
不管是登录shell还是非登录shell,如果想要指定用户触发变量文件的内容的话,写入~username/.bashrc文件
不管是登录shell还是非登录shell,如果想要系统所有的用户都生效的话,写入/etc/bashrc文件
补充:用户的登出shell触发的变量文件是~username/.bash_logout
当前Shell:接收你当前键盘输入指令的那个 Bash Shell进程。
子 Shell:是继承当前 Shell 环境的副本,但在其中执行的命令所产生的变量赋值和环境改变,不会影响父 Shell 环境。意义是变量和操作不会互相干扰。
常见的产生子Shell的场景(都属于非登录Shell):
在当前终端输入
bash命令管道符右侧的操作,如
ls | grep txt通过直接执行的方式运行一个脚本
./script.sh圆括号组内的所有命令在子 Shell 执行,不影响主Shell
(cd /tmp; ls)命令放入后台执行
sleep 100 &
在当前 Shell 的“子进程”中运行,不算作子Shell:
vim, python, ls等,它们不是Shell内置的命令,而是硬盘上的一个外置程序文件。
2、Bash Shell提供的功能
绝大多数主流发行版(如 Ubuntu、CentOS、Debian、Fedora)的默认交互 Shell 都是 Bash。
2.1、history
不同的终端所看到的history内容不同,默认缓存1000条
history -c
清空当前会话的历史列表,不会删除 ~/.bash_history 文件中的内容。
history -w
将当前会话历史写入历史文件,由于是覆盖式,因此通过history -c && history -w 就可以清空历史记录。需要注意的是如果开启了多个会话,需要退出其他的,在最后一个会话中进行。
history n
查看最近的n条历史记录
! 历史命令拓展
历史扩展(! 相关用法)不需要先执行 history 命令,它直接读取的是 Bash 在内存中维护的历史列表。
!!直接执行上一条命令!n直接执行第n条历史命令!-n倒数第n条命令!$引用上一条命令的最后一个参数!^引用上一条命令的第一个参数!*引用上一条命令的所有参数Alt + .组合键把上一条命令的最后一个参数直接调出来,可多次回溯!string最近一条以 string 开头的命令!?string?最近一条包含 string 的命令
Ctrl+R 历史命令搜索
按下Ctrl + R,会提示进入reverse-i-search,输入搜索关键词,Bash会立即从历史中反向(从最近到最早)查找命令,并显示匹配结果。
直接按下回车键:这将直接执行找到的命令。
按下右方向键(->)或者Esc:这会在终端中完整地打出该命令,但不执行,你可以对其进行编辑修改,确认无误后再按下回车键执行。
按 Ctrl + R 继续:查找下一个更早的匹配项。
2.2、通配符
通配符用于匹配文件名称和目录名称,需要与处理文本的正则表达式区分开。
* 表示匹配任意多个任意字符(包括0个)
比如 a* 可以匹配 abc、a123、a!@#,但不能匹配 bcd,因为开头必须是 a。
rm -rf * 删除当前目录任何名称的文件
有一个例外,*不会匹配路径分隔符 /。这意味着 *.txt 只能匹配当前目录下的文本文件,而不能匹配子目录里的 sub/file.txt。如果想匹配子目录,需要用 */*.txt
?表示匹配任意单个字符
ll file? 匹配fileX,但不会匹配到fileXX(需要用file??)
[0-9] 匹配任意单个数字
如果要匹配file100的话,必须file[0-9][0-9][0-9]
file[abc] 匹配括号内的任意单个字母
filea fileb filec都能匹配到
[a-z] 匹配所有的单个小写字母
$ touch a.txt b.txt c.txt D.txt 1.txt
$ ls [a-z].txt
a.txt b.txt c.txt[^abc] 匹配除括号内以外的所有字符
$ touch a.txt b.txt c.txt d.txt e.txt
$ ls [^abc].txt
d.txt e.txt[[:alpha:]] 忽略大小写,匹配任意字母
外面的[]表示一个整体,否则[:alpha:]只是表示逐个匹配括号内的这几个字符,等同于[a-zA-Z]
$ touch a.txt B.txt 1.txt _x.txt
$ ls [[:alpha:]].txt
a.txt B.txt[[:digit:]] 匹配任意单个数字
等同于[0-9]
[[:upper:]] 匹配大写字母
等同于[A-Z]
[[:lower:]] 匹配小写字母
等同于[a-z]
[[:alnum:]] 匹配数字和字母(忽略大小写)就是alpha + number的意思
等同于[0-9a-zA-Z]
$ touch a.txt B.txt 1.txt _x.txt
$ ls [[:alnum:]].txt
1.txt B.txt a.txt[[:space:]] 匹配空格
等同于[ \t\n],要注意在文件名中,空格通常被 shell 当作参数分隔符,所以直接这样用很容易出错。更好的写法是配合引号或转义
$ touch "a b.txt" "ab.txt" "a_b.txt"
$ ls *[[:space:]]*.txt
'a b.txt'[[:punct:]] 匹配除空格、字母、数字以外的字符
$ touch a.txt _x.txt !y.txt 1.txt
$ ls [[:punct:]].txt
!y.txt _x.txt2.3、$命令扩展符号
$(命令)和`命令`在功能上基本等效,都用于命令替换:把命令的输出结果作为另一个命令的参数。(` 的用法已经过时)
1.简单命令替换
$ echo "Today is $(date +%F)"
Today is 2026-10-08
$ echo "Today is `date +%F`"
Today is 2026-10-082.作为命令参数
#which命令输出了bash的路径
$ ls -l $(which bash)
-rwxr-xr-x 1 root root 1396520 Jan 1 2025 /usr/bin/bash3.赋值给变量
$ current_dir=$(pwd)
$ echo "$current_dir"
/home/user4.在脚本的循环中使用
# 遍历命令输出
$ for file in $(ls *.txt); do
echo "Processing: $file"
done2.4、{}大括号扩展
批量创建file1 到 file10 文件
touch file{1..10}
touch file{a..z}
touch file {1..1000..2}步长为2如果范围是不连续的,那么需要使用 , 进行隔开
touch file{a,1,b,2} #分别创建四个文件
mkdir /opt/{txt,pdf}巧用{}进行备份
cp passwd{,.bak} 大括号扩展会将passwd与括号中逗号隔开的内容逐个拼接,也就是说第一项是passwd + 空字符串,第二项是passwd + .bak,也就是表示cp passwd passwd.bak
2.4、输入输出重定向和管道符 |
文件描述符(File Descriptor,fd)
文件描述符本质上是一个非负整数,进程通过它来标识自己打开的文件或其他 I/O 资源。例如,在 Linux 中运行一个程序,它需要读取 test.txt:
1.程序向 Linux 内核申请打开 test.txt。
2.内核完成打开操作,并返回一个整数,例如 3。
3.程序以后想读取这个文件,就可以使用文件描述符 3。
4.不再需要这个文件时,程序可以关闭描述符 3。
5.FD 是由进程持有的。两个不同的进程都可能拥有 FD 3,但它们的 FD 3 完全可以指向不同的资源。

假设在终端中执行:
cat test.txt
cat 程序通常通过 FD 0 获取标准输入,并通过 FD 1 输出文件内容。由于标准输出默认连接到终端,因此内容就显示在屏幕上。
这0、1、2这三个 FD 通常在进程启动时已经存在,所以程序随后调用 open() 打开一个普通文件时,往往会拿到 3。
输出重定向
command > file
对于命令程序而言,它只知道标准输出是 FD 1,而不需要知道 FD 1 最终连接的是终端、普通文件还是管道。
重定向符号>把 FD 1 指向 file,原本要输出到屏幕上的内容就保存到了文件中,之所以这种操作叫重定向,因为本质上我们就是在就是修改 fd 的指向。
command >> file
表示追加标准输出重定向,在文件的末尾追加内容,而不是覆盖文件的内容
command 2> file
错误重定向,将原本输出在屏幕上的错误信息,重定向到文件中去
一个最常见的用法是command 2> /dev/null ,/dev/null 是一个字符设备文件,任何写入它的数据都会被立即丢弃,永远写不满,读取它时立即返回 EOF(文件结束)。因此可以实现命令的报错信息丢弃,只看到正常结果的效果。
command 2>> file
追加标准错误重定向,同理,在文件的末尾追加内容,而不是覆盖文件的内容
command &> file
把 fd 1 和 2 都指向 file,也就是命令的输出无论报错还是正确与否都重定向到文件中,如果我们使用command &> /dev/null ,那么命令的结果就都不会显示。
&>file 等同于>file 2>&1 ,先把 fd 1指向 file,然后把 fd 2指向 fd 1 当前所指向的地方。&1 表示右侧的 1 是一个文件描述符,而不是名为 1 的文件。
command &>> file
同理,在文件的末尾追加内容
输入重定向
command < input.txt
将 input.txt 作为命令的标准输入,相当于让 FD 0 指向这个文件。
command << EOF
Hello
Linux
EOF将命令中指定分隔符之间的多行文本作为输入。
command <<< "Hello Linux"将一个字符串作为命令的标准输入。
管道符
管道符连接多个命令,将面前命令的执行结果作为后面命令的参数进行使用
命令1 | 命令2 | 命令3 | 命令4 ...
echo 123 | passwd --stdin test 给test用户修改密码, 密码为123
在这个例子中,