shell分哪两类(shell是如何被解析的)

目录

一、sed的经典示例

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

二、awk的经典示例

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

三、shell解析的基本步骤

大括号扩展

波浪线扩展

变量替换扩展

算术扩展

文件通配符扩展

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

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

引号去除

搜索命令

fork exec

执行命令

eval命令的用法


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

shell分哪两类(shell是如何被解析的)(1)

shell分哪两类(shell是如何被解析的)(2)

一、sed的经典示例

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

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

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

shell分哪两类(shell是如何被解析的)(3)

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

如何解决?

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

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

shell分哪两类(shell是如何被解析的)(4)

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

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

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

shell分哪两类(shell是如何被解析的)(5)

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

第二个$ 属于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

shell分哪两类(shell是如何被解析的)(6)

这样也是可以的

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

shell分哪两类(shell是如何被解析的)(7)

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

shell分哪两类(shell是如何被解析的)(8)

二、awk的经典示例使用awk输出hello world,在hello后增加单引号

redirect]# awk 'BEGIN{print "hello world"}' hello world

shell分哪两类(shell是如何被解析的)(9)

redirect]# awk 'BEGIN{print "hello'"'"' world"}' hello' world 这样拆解开来看 'BEGIN{print "hello' "'" ' world"}'

shell分哪两类(shell是如何被解析的)(10)

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分哪两类(shell是如何被解析的)(11)

三、shell解析的基本步骤

shell分哪两类(shell是如何被解析的)(12)

shell分哪两类(shell是如何被解析的)(13)

大括号扩展

生成数字

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

shell分哪两类(shell是如何被解析的)(14)

批量创建文件:

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

shell分哪两类(shell是如何被解析的)(15)

shell分哪两类(shell是如何被解析的)(16)

shell分哪两类(shell是如何被解析的)(17)

波浪线扩展

echo ~ 输出家目录

shell分哪两类(shell是如何被解析的)(18)

shell分哪两类(shell是如何被解析的)(19)

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

shell分哪两类(shell是如何被解析的)(20)

shell分哪两类(shell是如何被解析的)(21)

echo ~- 输出上一级目录

shell分哪两类(shell是如何被解析的)(22)

shell分哪两类(shell是如何被解析的)(23)

变量替换扩展

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

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

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

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

shell分哪两类(shell是如何被解析的)(24)

二次单词拆分:

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

shell分哪两类(shell是如何被解析的)(25)

算术扩展

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

shell分哪两类(shell是如何被解析的)(26)

文件通配符扩展

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

shopt [-psu] [optname …]

-s 开启某个选项.

-u 关闭某个选项.

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

shell分哪两类(shell是如何被解析的)(27)

shell分哪两类(shell是如何被解析的)(28)

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

shopt -s dotglob 开启点通配符扩展

dotglob If set, bash includes filenames beginning with a `.' in the results of pathname expansion.

shell分哪两类(shell是如何被解析的)(29)

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

shell分哪两类(shell是如何被解析的)(30)

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

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

shell分哪两类(shell是如何被解析的)(31)

shell分哪两类(shell是如何被解析的)(32)

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.

shell分哪两类(shell是如何被解析的)(33)

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

shell分哪两类(shell是如何被解析的)(34)

引号去除

cat "/proc/self/cmdline"

shell分哪两类(shell是如何被解析的)(35)

shell分哪两类(shell是如何被解析的)(36)

搜索命令fork exec

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

执行命令

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

shell分哪两类(shell是如何被解析的)(37)

shell分哪两类(shell是如何被解析的)(38)

shell解析命令行的流程

shell分哪两类(shell是如何被解析的)(39)

shell分哪两类(shell是如何被解析的)(40)

看了这个流程你会发现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分哪两类(shell是如何被解析的)(41)

解释如上的shell脚本

shell分哪两类(shell是如何被解析的)(42)

shell分哪两类(shell是如何被解析的)(43)

shell分哪两类(shell是如何被解析的)(44)

shell分哪两类(shell是如何被解析的)(45)

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

shell分哪两类(shell是如何被解析的)(46)

命令行处理步骤

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

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

shell分哪两类(shell是如何被解析的)(47)

shell分哪两类(shell是如何被解析的)(48)

对于读入的管道,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/.*$(($$00) ll $(type -pathcc) ~alice/.*$(($$00)) 将其分割成一系列单词。 ll 并非关键字,是小写l,因此第 2 步什么都不做 1。 ls -l $(type -path cc) ~alice/.*$(($$00)) 将别名 ll 替换成 ls -l。 然后shell 重复第 1~3 步; 第 2 步会将 ls -l 分割为 2 个单词。ls-l$(type -pathcc) ~alice/.*$(($$00)) 什么都不做。 ls -l $(type -path cc) /home/alice/.*$(($$00)) 将~alice 扩展成 /home/alice。 ls -l $(type -path cc) /home/alice/.*$((253700)) 将 $$ 替换成 2537。 ls -l /usr/bin/cc/home/alice/.*$((253700)) 对 type -path cc 执行命令替换。 ls -l /usr/bin/cc/home/alice/.*537 对算术表达式 253700 求值。 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

shell分哪两类(shell是如何被解析的)(49)

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

shell分哪两类(shell是如何被解析的)(50)

shell分哪两类(shell是如何被解析的)(51)

,

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com

    分享
    投诉
    首页