shell是如何被解析的?(shell 进阶)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 经常写shell,那么shell如何被解析的呢?

经常写shell,那么shell如何被解析的呢?

397837621785489dadb5ba02ea559ebd.png

一、sed的经典示例


$符号在shell中解析为变量,但是在sed中代表文件的最后一行。

如何显示/etc/passwd 的倒数第三行


redirect]# sed -n '$-2p' /etc/passwd

这个明显是不行的,sed内部有一个行号计数器,一行一行读取直到最后一行 ,$才是最后一行的行号。


如何解决?


先用wc -l计数,然后变量传进去再打印倒数第三行。

redirect]# line=25
redirect]# sed -n "${line}p" /etc/passwd

注意不能用单引号,单引号属于强引用,无法将变量解析。


如何要同时显示最后一行和倒数第三行?

 redirect]# sed -n "${line}p;$p" /etc/passwd
tcpdump:x:72:72::/:/sbin/nologin

这样为何只显示了倒数第三行内容呢?


第二个$ 属于sed的最后一行,不应该暴露给shell解析。

redirect]# sed -n "${line}p;"'$p' /etc/passwd
tcpdump:x:72:72::/:/sbin/nologin
rpcuser:x:29:29:RPC Service User:/var/lib/nfs:/sbin/nologin

这样也是可以的

redirect]# sed -n "${line}p;\$p" /etc/passwd
tcpdump:x:72:72::/:/sbin/nologin
rpcuser:x:29:29:RPC Service User:/var/lib/nfs:/sbin/nologin
redirect]# sed -n "${line}""p;\$p" /etc/passwd
tcpdump:x:72:72::/:/sbin/nologin
rpcuser:x:29:29:RPC Service User:/var/lib
redirect]# sed -n ${line}"p;\$p" /etc/passwd
tcpdump:x:72:72::/:/sbin/nologin
rpcuser:x:29:29:RPC Service User:/var/lib/nfs:/sbin/nologin

二、awk的经典示例

使用awk输出hello world,在hello后增加单引号

redirect]# awk 'BEGIN{print "hello world"}'
hello world
 redirect]# awk 'BEGIN{print "hello'"'"' world"}'
hello' world
这样拆解开来看
'BEGIN{print "hello'    "'"    ' world"}'
redirect]# awk "BEGIN{print \"hello' world\"}"
hello' world
047是单引号的ASSIC值
 redirect]# awk 'BEGIN{print "hello\047 world"}'
hello' world
# print 中双引号的值都保留给awk
awk -v q="'" 'BEGIN{print "hello"q" world"}'
hello' world

三、shell解析的基本步骤


0d9d855baa7c463397eee9ce359c9223.png

ba094c96191c4bc29a7f4a2237dbc2a6.png

大括号扩展


生成数字

 to_delete]# echo {1..10}
1 2 3 4 5 6 7 8 9 10
[root@to_delete]# 
[root@to_delete]# echo {a..e}
a b c d e

批量创建文件:

to_delete]# touch /tmp/{a..d}.log

51d1948e2249493890584eba1b4cb4bf.png

波浪线扩展


echo ~ 输出家目录

c10463d5b6a64e689faa2284b0005c6e.png

echo ~+ 输出当前目录,和pwd等效

bc28dcd71934484c9eecc2fc41e0a1e7.png

echo ~- 输出上一级目录

47f9f15d0c3b49a5a6be06d4b87e0dd0.png

变量替换扩展


不仅可以解释变量,也可以解释变量表达式。


例如截取字符s之前的内容。


%%s* 贪婪删除从左到右。

~]# echo $name
ninesun
~]# echo ${name%%s*}
nine

二次单词拆分:

~]# echo $(echo -e "hello\nworld")
hello world
~]# echo "$(echo -e "hello\nworld")"
hello
world

算术扩展


to_delete]# a=4
[root@to_delete]# echo $((a+5))
9

文件通配符扩展


shopt 命令用于显示和设置shell中的行为选项,通过这些选项以增强shell易用性。


shopt [-psu] [optname …]


-s 开启某个选项.

-u 关闭某个选项.

-p 列出所有可设置的选项.

8f8a9b12d0a7461586a52c28878c2a66.png

1、如何解决*无法匹配以.开头的文件?


 dotglob If set, bash includes filenames beginning with a `.' in the results of pathname expansion.
tmp]# ls /root/*ssh
ls: cannot access '/root/*ssh': No such file or directory  # 匹配不到
[root@tmp]# shopt -s dotglob
[root@tmp]# ls /root/*ssh
authorized_keys

2、如何解决递归到子目录进行匹配?


直接搜索,找不到子目录fork中的文件。

c9b474fe51434adaa4c7868390a71f70.png

shopt -s globstar  开启递归搜索文件通配符扩展

globstar
                      If set, the pattern ** used in a pathname expansion context will match all files and zero or more directories and subdirectories.  If the
                      pattern is followed by a /, only directories and subdirectories match.

grep -l "printf" **/*.c


**代表递归当前目录


-l 只列出文件名,而不是内容。这也是grep比较好用的参数.

tmp]# shopt -s globstar
[root@tmp]# grep -l "printf" *.c
fork.c
[root@tmp]# grep -l "printf" **/*.c
fork.c
test/fork_1.c
[root@tmp]# grep -l "printf" */*.c
test/fork_1.c
# ** 不仅仅递归一层目录,多级子目录同样可以递归. 
tmp]# grep -l "printf" **/*.c
fork.c
fork/fork_2/fork_2.c
test/fork_1.c

c

test/fork_1.c

引号去除


cat "/proc/self/cmdline"

3272536023844bcb9505e82844c0f843.png

搜索命令


cce31d6df0d34b82a7375cc5b0d36f27.png

fork + exec


fork一个子bash进程,在子bash进程中加载exec替换子进程bash并执行起来

执行命令


子bash的进程退出码交还给父进程。

1e291ebcec384aa3a91b47ce056b9627.png

shell解析命令行的流程

905f2cae20934718b6f235e64f356381.png

看了这个流程你会发现shell编程中单引号、双引号的真正含义。


例如为何  echo "~" 和 echo ~的输出会不同


echo "{1..10}" 和echo {1..10}的输出结果会不同?


示例

name=longshuai
a=24
echo -e "some files:" ~/i* "\nThe date:$(date +%F)\n$name's age is $((a+4))" >/tmp/a.log

解释如上的shell脚本

447b3531ea9b4584accbfda52c424eb7.png

e128ad6656ca4ceaa48155a0becdb68e.png

eval命令的用法


\$ 被当成普通的$符号而不是变量的标识。


$a 替换为hello


eval echo $hello  此时eval不执行自己,就变成了echo $hello

[root@hadoop100 ~]#a=hello
You have mail in /var/spool/mail/root
[root@hadoop100 ~]#
[root@hadoop100 ~]#hello=ninesun
[root@hadoop100 ~]#
[root@hadoop100 ~]#echo $a
hello
[root@hadoop100 ~]#echo \$a
$a
[root@hadoop100 ~]#
[root@hadoop100 ~]#eval echo \$a
hello
[root@hadoop100 ~]#
[root@hadoop100 ~]#eval echo \$$a
ninesun
[root@hadoop100 ~]#eval echo $$a
3671a
[root@hadoop100 ~]#
[root@hadoop100 ~]#
[root@hadoop100 ~]#eval echo $\$a
3671a
[root@hadoop100 ~]#eval echo \$$a
ninesun

命令行处理步骤


--update 2022年2月11日09:40:54


要想成为真正的 shell 脚本编程专家(或者为了调试一些棘手的问题),你需要理解命令行处理过程涉及的各个步骤,尤其是这些步骤的先后顺序。shell 从 STDIN 或脚本中读取的每一行被称为管道,因为其中包含了由零个或多个管道字符(|)分隔的一个或多个命令。

5a26964ed6b64e56b8c54a14302799b0.png

对于读入的管道,shell 会将其分解成命令,设置管道的 I/O,然后对每个命令执行下列操作。将命令分割成由一组固定的元字符空格、制表符、换行符、;、(、)、<、>、|、&)分隔的词法单元(token)


词法单元的类型包括单词、关键字、I/O 重定向、分号。


检查每个命令的第一个词法单元是否为不带引号或反斜线的关键字。如果属于起始关键字,例如 if 或其他控制结构的开头、function、{、(,那么该命令属于复合命令。


shell 会在内部为其完成相关准备工作,读取下一个命令,并重头开始这个过程。如果关键字不是复合命令的开头(例如,then、else、do 这种属于控制结构“中间”的部分;fi、done 这种属于控制结构“结束”的部分;或者是逻辑运算符),则 shell 会提示语法错误。


对照别名列表检查每个命令的第一个单词。如果有匹配,将单词替换成别名定义,然后返回第 1 步;否则,继续往下进行第 4 步。


这种方式允许出现递归别名,也允许为关键字定义别名(例如,alias aslongas=while 或 alias procedure=function)。


执行花括号扩展。例如,将 a{b,c} 扩展为 ab ac。


如果波浪线位于单词的开头,将其替换成用户的主目录($HOME)。例如,将 ~user 替换成 user 的主目录。


对以美元符号($)起始的表达式执行参数(变量)替换。


对形如 $(string) 的表达式执行命令替换。


对形如 $((string)) 的算术表达式进行求值。


将命令行中经过参数替换、命令替换、算术求值得到的结果再次分割成一系列单词。


这次使用 $IFS 所包含的字符作为分隔符,不再使用第 1 步中的那组元字符。对出现的 *、?、[] 执行路径扩展,也就是通配符扩展。


将第一个单词作为命令,按照下列顺序查找其来源:先按照函数名,再作为内建命令,接着作为 $PATH 所包含目录中的文件。设置好 I/O 重定向和其他事宜后,执行该命令。


步骤着实不少,甚至这还不是全部!在继续往下进行之前,我们应该先用一个示例来明晰这个过程。假设要执行下列命令:


alias ll = "ls -l"


再进一步假设用户 alice 的主目录(/home/alice)有一个名为 .hist537 的文件,还有一个双美元符号变量,其值为2357(记住)


保存的是进程 ID,这个数字在所有运行的进程中是唯一的)。现在我们来看看 shell 是如何处理下列命令的。

ll $(type -path cc) ~alice/.*$(($$%1000)
ll $(type -pathcc) ~alice/.*$(($$%1000)) 将其分割成一系列单词。
ll 并非关键字,是小写l,因此第 2 步什么都不做 1。
ls -l $(type -path cc) ~alice/.*$(($$%1000)) 将别名 ll 替换成 ls -l。
然后shell 重复第 1~3 步;
第 2 步会将 ls -l 分割为 2 个单词。ls-l$(type -pathcc) ~alice/.*$(($$%1000)) 什么都不做。
ls -l $(type -path cc) /home/alice/.*$(($$%1000)) 将~alice 扩展成 /home/alice。
ls -l $(type -path cc) /home/alice/.*$((2537%1000)) 将 $$ 替换成 2537。
ls -l /usr/bin/cc/home/alice/.*$((2537%1000)) 对 type -path cc 执行命令替换。
ls -l /usr/bin/cc/home/alice/.*537 对算术表达式 2537%1000 求值。
ls-l /usr/bin/cc/home/alice/.*537 什么都不做。
ls -l /usr/bin/cc/home/alice/.hist537 将通配符表达式 .*537 替换成文件名。在 /usr/bin 中找到命令 ls。
执行包含选项 -l 和两个参数的 /usr/bin/ls。
1这里提到的第 x 步对应前文中相应编号的步骤。
——译者注尽管这些步骤相当直观,但只是部分而已。还有 5 种方式可以改变该流程:引用;使用 command、builtin、enable;使用高级命令 eval

单引号('')能够完全绕过包括别名在内的第 1~10 步。单引号中的所有字符全部保持不变。单引号中不能再出现单引号,就算在其之前加上反斜线(\)也没用。双引号("")能够绕过第 1~4 步以及第 9~10 步。也就是说,它会忽略管道字符、别名、波浪号替换、通配符扩展以及通过分隔符(如空白字符)将双引号中的内容分割成一系列单词。但是,双引号仍会执行参数替换、命令替换以及算术表达式求值。双引号中可以出现双引号,在其之前加上反斜线(\)即可。另外,必须使用反斜线对具有特殊含义的 $、'(古老的命令替换分隔符)、\ 进行转义。表 C-1 给出了一个简单的示例,其中演示了引用是如何工作的。假设执行的语句是 person=hatter,用户 alice 的主目录是 /home/alice

f4a71d558cab4250b69065ac7f3a4dc7.png

目录
相关文章
|
3月前
|
Java Shell Linux
【Linux入门技巧】新员工必看:用Shell脚本轻松解析应用服务日志
关于如何使用Shell脚本来解析Linux系统中的应用服务日志,提供了脚本实现的详细步骤和技巧,以及一些Shell编程的技能扩展。
55 0
【Linux入门技巧】新员工必看:用Shell脚本轻松解析应用服务日志
|
3月前
|
API C# Shell
WPF与Windows Shell完美融合:深入解析文件系统操作技巧——从基本文件管理到高级Shell功能调用,全面掌握WPF中的文件处理艺术
【8月更文挑战第31天】Windows Presentation Foundation (WPF) 是 .NET Framework 的关键组件,用于构建 Windows 桌面应用程序。WPF 提供了丰富的功能来创建美观且功能强大的用户界面。本文通过问题解答的形式,探讨了如何在 WPF 应用中集成 Windows Shell 功能,并通过具体示例代码展示了文件系统的操作方法,包括列出目录下的所有文件、创建和删除文件、移动和复制文件以及打开文件夹或文件等。
76 0
|
5月前
|
Python
Python面向对象进阶:深入解析面向对象三要素——封装、继承与多态
Python面向对象进阶:深入解析面向对象三要素——封装、继承与多态
|
5月前
|
Shell 开发者
Shell 函数深入解析与实践
了解 Shell 函数的基础,包括定义、参数传递及返回值。函数定义有多种语法,如 `function func() {...}` 或 `func() {...}`。参数通过 `$1`, `$2` 等访问,`$@` 代表所有参数。`return` 用于返回退出状态码(0-255),非数值数据需用 `echo`。正确获取函数返回值应立即检查 `$?`,例如:`result=$?`。实践中不断探索和学习!
37 1
|
5月前
|
前端开发 开发者
CSS文本样式全面解析:从基础到进阶
CSS文本样式全面解析:从基础到进阶
|
5月前
|
Unix Shell Perl
技术心得:实例解析shell子进程(subshell)
技术心得:实例解析shell子进程(subshell)
|
6月前
|
C语言
C语言进阶⑫(指针下)(指针和数组笔试题解析)(杨氏矩阵)(上)
C语言进阶⑫(指针下)(指针和数组笔试题解析)(杨氏矩阵)
42 0
|
6月前
|
算法 C语言
C语言进阶⑫(指针下)(指针和数组笔试题解析)(杨氏矩阵)(下)
C语言进阶⑫(指针下)(指针和数组笔试题解析)(杨氏矩阵)
29 0
|
6月前
|
C语言
C语言进阶⑫(指针下)(指针和数组笔试题解析)(杨氏矩阵)(中)
C语言进阶⑫(指针下)(指针和数组笔试题解析)(杨氏矩阵)
37 0
|
6月前
|
弹性计算 运维 Shell
每天解析一个shell脚本(86)
【4月更文挑战第28天】shell脚本解析及训练(86)
55 0

推荐镜像

更多