TypechoJoeTheme

MetMan's Blog

网站页面

Shell expansion学习

MetMan博 主
2023-12-03
/
0 评论
/
143 阅读
/
1503 个字
/
百度已收录
12/03
本文最后更新于 2023年12月03日,已超过 291天没有更新。如果文章内容或图片资源失效,请留言反馈,我会及时处理,谢谢!

Bash在执行输入的命令前,会对输入的字符进行处理,这个过程叫(字符)展开。

我们可以通过shell命令echo查看展开过程。

$ echo this is a      test
this is a test  #a与test之间只保留了一个空格
$ echo *
Desktop LauncherFolder MyDocuments

shell命令在执行前将*展开成了其它东西。

shell展开类型

下面通过简单的例子来学习shell命令展开。

比如一个常见的需求:打印出指定目录中每一个文件用于后续处理。

$ cd ~/sample     #其中'b c.txt'文件名有空格
 a.txt  'b c.txt'   d.txt

下面是示例脚本:

#!/bin/bash
num_files=0
for file in `ls ~/sample/*.txt`
do
    echo $file
    num_files=$((num_files + 1))
done

echo num of files is $num_files

波浪线展开

~/sample/*.txt

Tilde expansion, ~会展开成指定用户的家目录$HOME,如果没有指定用户,则展开成当前用户家目录。

$ echo ~foo
~foo    
$ echo ~
/home/user
$ echo ~/foo
/home/user/foo

文件名展开

单词分割后,除非设置了 -f 选项,否则 Bash 会扫描每个单词,查看是否有字符 *? 或 [。如果出现这些字符之一,则该单词将被视为PATTERN,并替换为与模式匹配的按字母顺序排序的文件名列表。以上例子会查找sample目录下匹配后缀名为.txt的文件名。

$ echo *.txt

命令替换

`ls ~/sample/*.txt`

命令替换就是将命令的输出结果展开使用。主要有两种形式,一种是$(command),另一种是(old-style)倒引号圈住命令。以上要替换命令是ls ~/sample/*.txt

Bash 通过执行 COMMAND 并将命令替换替换为命令的标准输出来执行展开,并删除任何尾随换行符。嵌入的换行符不会被删除,但可能会在单词分割期间删除。

$ echo $(ls)
$ echo `ls`

#观测以下两者区别
$ echo $(ls -l )  #只输出了一行
$ ls -l 

命令替换可以嵌套。比如:

$ a=$(file $(ls *.txt))
$ echo $a
LICENSE.txt: ASCII text

如果替换出现在双引号内,则不会对结果执行单词分割和文件名展开。

shell参数和变量展开

也就是变量引用,参数展开为实际值。例子中是for循环文件列表的每一个文件。

    $ echo $file

算术表达式展开

num_files=$((num_files + 1))

$((expr))算术表达式展开,可以用作整数计算。在算术表达式中空格并不重要,并且表达式可以嵌套。

表达式被视为在双引号内,但括号内的双引号不被特殊处理。表达式中的所有标记都会经历参数扩展、命令替换和引号删除。算术替换可以嵌套。

运算符与 C 编程语言中的运算符大致相同。按优先级递减顺序,列表如下所示:

在可能的情况下,Bash 用户应尝试使用带方括号的语法:

$[ EXPRESSION ]

注意因为展开只是支持整数除法,所以结果是整数(地板除)

    $ echo $((1+4))
    $ echo $(($((5**2))*3))
    $ echo $((5/2))

单词分割

shell 扫描参数扩展、命令替换和算术扩展的结果,这些结果未在双引号内发生,以进行单词拆分。

shell 将$IFS的每个字符视为分隔符,并将其他扩展的结果拆分为这些字符上的单词。如果未设置 IFS,或者其值恰好为默认值“<空格><制表符><换行符>”,则任何 IFS 字符序列都用于分隔单词。如果 IFS 的值不是默认值,则只要空格字符位于 IFS(IFS 空格字符)的值中,单词开头和结尾就会忽略空格字符“空格”和“Tab”的序列。IFS 中任何非 IFS 空格的字符以及任何相邻的 IF 空格字符都会分隔字段。IFS 空格字符序列也被视为分隔符。如果 IFS 的值为 null,则不会发生分词。

如果没有展开,就不执行单词分割。

除了上述例子展示的几种展开,还有其它展开,包括:

花括号展开

花括号展开(Brace expansion)是一种可以生成任意字符串的机制。花括号表达式本身可能包含一个由逗号分开的字符串列表,或者一系列整数,或者单个的字符串。这种模式不能嵌入空白字符。花括号展开模式可能包含一个开头部分叫做报头,一个结尾部分叫做附言.

花括号展开可以嵌套。

$ echo a{A,B,C}b  # a是报头,b是附言
aAb aBb aCb
$ echo {1..10}   # 整数序列
1 2 3 4 5 6 7 8 9 10
$ echo {A..Z}     # 字符序列
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
$ echo {Z..A}  #支持倒序排列
Z Y X W V U T S R Q P O N M L K J I H G F E D C B A
$ echo {10..1} 
10 9 8 7 6 5 4 3 2 1
$ echo a{A{1,2},B{1,2}}b   #嵌套
aA1b aA2b aB1b aB2b

花括号展开在任何其他展开之前执行,并且结果中将保留其他扩展所特有的任何字符。

$  echo re{*,?}md
readme.md re?md

进程替换

<(LIST)

or

>(LIST)

展开执行顺序

如果系统支持进程替换展开,与波浪号、参数变量、算术展开、命令替换同时执行。

示例脚本分析

文章开头的脚本示例执行结果:

/home/user/sample/a.txt
/home/user/sample/b
c.txt
/home/user/sample/d.txt
num of files is 4

我们看到输出的结果和预料的有差异,问题就出在单词分割上。

脚本展开顺序:

  • 波浪号展开

~/sample/*.txt展开为/home/user/sample/*.txt

  • 文件名展开

单词分割后,有*星号,文件名展开为/home/user/sample/a.txt,/home/user/sample/b c.txt,/home/user/sample/d.txt

  • 命令替换展开
/home/user/sample/a.txt /home/user/sample/b c.txt /home/user/sample/d.txt
  • 单词分割

执行完命令替换后再进行单词分割,由于/home/user/sample/b c.txt有空格,单词分割时将其展开为两个单词,分别为/home/user/sample/bc.txt,最终输出错误的结果。

脚本修正

将脚本第3行替换为

for file in ~/sample/*.txt

不需要使用ls命令列出文件列表,而是通过文件名展开即可,其在单词分割后面执行。输出结果为:

/home/user/sample/a.txt
/home/user/sample/b c.txt
/home/user/sample/d.txt
num of files is 3

如何控制展开

shell 提供了一种叫做引用的机制,来有选择地禁止不需要的展开

  • 双引号引用

使用双引号,我们可以禁止以下展开

  • 单词分割
  • 文件名展开
  • 波浪号展开
  • 花括号展开

以下几种仍然展开

  • 参数展开
  • 算术展开
  • 命令替换
  • $,\,倒引号

在默认情况下,单词分割机制会在单词中寻找空格,制表符,和换行符,并把它们看作单词之间的界定符。它们只作为分隔符使用。单词分割被禁止,内嵌的空格也不会被当作界定符,它们成为参数的一部分。 一旦加上双引号,我们的命令行就包含一个带有一个参数的命令。

  • 单引号

禁止所有展开。

参考资料

https://tldp.org/LDP/Bash-Beginners-Guide/html/sect_03_04.html

https://www.gnu.org/software/bash/manual/html_node/Shell-Expansions.html

shell
朗读
赞(0)
赞赏
感谢您的支持,我会继续努力哒!
版权属于:

MetMan's Blog

本文链接:

https://blog.metman.top/index.php/archives/77/(转载时请注明本文出处及文章链接)

评论 (0)

互动读者

标签云

最新回复

暂无回复

登录
X
用户名
密码