Administrator
发布于 2026-04-13 / 21 阅读
0
0

RHCSA/HCIA⑥:Shell

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进程解释执行的。有以下几种常见的容易使人混淆困惑的表述:

  1. "shell终端" :shell不是终端的一部分,而是终端中运行的程序

  2. "在终端中执行shell命令" :准确来说是在终端中运行命令,这些命令由shell解释执行。终端本身无法执行命令,它只是运行shell的环境

  3. "终端是命令行界面":命令行界面是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.txt

2.3、$命令扩展符号

$(命令)和`命令`在功能上基本等效,都用于命令替换:把命令的输出结果作为另一个命令的参数。(` 的用法已经过时)

1.简单命令替换

$ echo "Today is $(date +%F)"
Today is 2026-10-08

$ echo "Today is `date +%F`"
Today is 2026-10-08

2.作为命令参数

#which命令输出了bash的路径
$ ls -l $(which bash) 
-rwxr-xr-x 1 root root 1396520 Jan  1  2025 /usr/bin/bash

3.赋值给变量

$ current_dir=$(pwd)
$ echo "$current_dir"
/home/user

4.在脚本的循环中使用

# 遍历命令输出
$ for file in $(ls *.txt); do
    echo "Processing: $file"
done

2.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 完全可以指向不同的资源。

文件描述符

名称

默认指向

用途

0

标准输入 (stdin)

终端键盘

读取输入

1

标准输出 (stdout)

终端屏幕

输出正常结果

2

标准错误 (stderr)

终端屏幕

输出错误信息

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

在这个例子中,


评论