目录

一、sed的经典示例

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

二、awk的经典示例

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

三、shell解析的基本步骤

大括号扩展

波浪线扩展

变量替换扩展

算术扩展

文件通配符扩展

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

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

引号去除

搜索命令

fork + exec

执行命令

eval命令的用法

经常写shell,那么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 "hello47 world"}'
hello' world
# print 中双引号的值都保留给awk
awk -v q="'" 'BEGIN{print "hello"q" world"}'
hello' world

三、shell解析的基本步骤

通配符什么代表一个字符_通配符什么代表任意多个字符_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

通配符什么代表任意多个字符_shell通配符_通配符什么代表一个字符

波浪线扩展

echo ~ 输出家目录

通配符什么代表任意多个字符_通配符什么代表一个字符_shell通配符

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

通配符什么代表一个字符_shell通配符_通配符什么代表任意多个字符

echo ~- 输出上一级目录

shell通配符_通配符什么代表一个字符_通配符什么代表任意多个字符

变量替换扩展

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

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

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

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

二次单词拆分:

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

算术扩展

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

文件通配符扩展

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

shopt [-psu] [optname …]

-s 开启某个选项.

-u 关闭某个选项.

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

通配符什么代表任意多个字符_通配符什么代表一个字符_shell通配符

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中的文件。

通配符什么代表任意多个字符_通配符什么代表一个字符_shell通配符

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”

通配符什么代表一个字符_shell通配符_通配符什么代表任意多个字符

搜索命令

fork + exec

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

执行命令

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

shell通配符_通配符什么代表一个字符_通配符什么代表任意多个字符

shell解析命令行的流程

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脚本

通配符什么代表任意多个字符_shell通配符_通配符什么代表一个字符

shell通配符_通配符什么代表一个字符_通配符什么代表任意多个字符

eval命令的用法

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

$a 替换为hello

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

通配符什么代表一个字符_shell通配符_通配符什么代表任意多个字符

[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通配符_通配符什么代表任意多个字符_通配符什么代表一个字符

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

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

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

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

对照别名列表检查每个命令的第一个单词。如果有匹配,将单词替换成别名定义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

shell通配符_通配符什么代表任意多个字符_通配符什么代表一个字符

限 时 特 惠: 本站每日持续更新海量各大内部创业教程,加站长微信免费获取积分,会员只需38元,全站资源免费下载 点击查看详情
站 长 微 信: thumbxmw