shell分哪两类(shell是如何被解析的)
目录
一、sed的经典示例
如何显示/etc/passwd 的倒数第三行
二、awk的经典示例
使用awk输出hello world,在hello后增加单引号
三、shell解析的基本步骤
大括号扩展
波浪线扩展
变量替换扩展
算术扩展
文件通配符扩展
1、如何解决*无法匹配以.开头的文件?
2、如何解决递归到子目录进行匹配?
引号去除
搜索命令
fork exec
执行命令
eval命令的用法
经常写shell,那么shell如何被解析的呢?
一、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解析的基本步骤
大括号扩展生成数字
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
波浪线扩展echo ~ 输出家目录
echo ~ 输出当前目录,和pwd等效
echo ~- 输出上一级目录
变量替换扩展不仅可以解释变量,也可以解释变量表达式。
例如截取字符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 列出所有可设置的选项.
1、如何解决*无法匹配以.开头的文件?shopt -s dotglob 开启点通配符扩展
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中的文件。
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
引号去除
cat "/proc/self/cmdline"
搜索命令fork exec
fork一个子bash进程,在子bash进程中加载exec替换子进程bash并执行起来
执行命令子bash的进程退出码交还给父进程。
shell解析命令行的流程
看了这个流程你会发现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脚本
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 或脚本中读取的每一行被称为管道,因为其中包含了由零个或多个管道字符(|)分隔的一个或多个命令。
对于读入的管道,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
单引号('')能够完全绕过包括别名在内的第 1~10 步。单引号中的所有字符全部保持不变。单引号中不能再出现单引号,就算在其之前加上反斜线(\)也没用。双引号("")能够绕过第 1~4 步以及第 9~10 步。也就是说,它会忽略管道字符、别名、波浪号替换、通配符扩展以及通过分隔符(如空白字符)将双引号中的内容分割成一系列单词。但是,双引号仍会执行参数替换、命令替换以及算术表达式求值。双引号中可以出现双引号,在其之前加上反斜线(\)即可。另外,必须使用反斜线对具有特殊含义的 $、'(古老的命令替换分隔符)、\ 进行转义。表 C-1 给出了一个简单的示例,其中演示了引用是如何工作的。假设执行的语句是 person=hatter,用户 alice 的主目录是 /home/alice
,免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com