Unix Shell 如何看待世界(扩展,引用)
太长不看
扩张
引用
结论
参考
太长不看
我每天都使用 Unix 终端,输入上百行终端命令,但有时,在输入命令后按下回车键时,会遇到一些意想不到的情况。这有时真的让人很沮丧,原来 Shell 的运作方式和我理解的不一样。我读了一些关于 Shell 如何响应命令的书籍(至少读了其中的一些章节),发现这真的很有意思,所以今天想和大家分享一下。希望对大家有所帮助。
因为这非常简单明了。仅使用echo命令,我们就可以探索 Shell 的几个有趣特性,也就是它的“魔力”。
扩张
每次我们在命令后按下回车键,Shell 都会在底层对其进行转换,然后再执行。这个过程称为扩展。扩展是指,当你输入内容时,它会被扩展成另一种形式,然后 shell 才会执行它。例如:* 对 Shell 来说意义重大。为了说明这一点,我将使用echo命令。你可能还记得,echo是一个简单的 Shell 内置函数,它会将我们输入的内容输出到标准输出。
phuong@Arch ~$ echo Hello world
Hello world
所以*通配符的意思是:匹配文件名中的任何字符。* 在被 shell 执行之前会被转换成其他字符,因此 echo 命令永远不会直接看到它。
phuong@Arch ~/kitchen$ echo *
bash device devops gateway go hook iuh js linux ml python
文件名扩展
事实上,这种行为是由一种机制实现的,这种机制通常被称为路径名扩展。我们知道,以句点字符开头的文件名(例如:.git)是隐藏的。路径名扩展遵循这一规则。扩展不会显示隐藏的文件。
phuong@Arch ~$ echo D*
Desktop Documents Downloads
但是我们可以通过以下命令显示所有隐藏文件:
phuong@Arch ~$ echo .*
. .. .3T .ansible .anydesk .bash_history .bash_logout .bash_profile .bashrc .bashrc.backup .boto .cache .config .dbshell .designer .docker
算术展开
借助算术展开功能,我们还可以将终端命令用作计算器,但它仅适用于整数(对于浮点数,我们需要像 bc 等工具)。尽管存在局限性,它在某些情况下仍然很有用,并且支持许多常用运算符。例如:, -, * , % , / , + 等。
算术展开遵循以下模式:
$((表达式)
phuong@Arch ~$ echo This month has $((4*7)) days
This month has 28 days
除了双括号之外,我们还可以使用单括号来嵌套子展开。例如:
phuong@Arch ~$ echo This year has $((((4*7)*12) + $((3 * 9)))) days
This year has 363 days
波浪号扩展
当我们在终端中使用~作为字符串的开头时,它会展开为指定用户的家目录。
假设我使用一个云端账户,该账户有很多用户,但我不知道他们的家目录在哪里,我只知道他们的用户名。这时,它就派上用场了。
phuong@devcloud1:~$ echo ~wingzero
/home/wingzero
phuong@devcloud1:~$ echo ~phuong
/home/phuong
支架扩张
大括号表达式本身可以包含以逗号分隔的字符串列表,也可以包含一系列整数或单个字符。
以逗号分隔的示例为例:
phuong@Arch ~/kitchen/ips$ echo A{A,B}
AA AB
同样,单个字符的范围
phuong@Arch ~$ 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
在我看来,这种扩展方式是最有趣的扩展机制之一,因为它在特定情况下非常有用。假设我想为子网范围内的每个网络 IP 地址创建一个文件夹。假设192.168.43.1 对应 192.168.43.13。我承认,四个月前我会这样做:`mkdir ... `。但这种方法容易出现拼写错误。这时,花括号扩展就派上用场了。
phuong@Arch ~/kitchen/ips$ mkdir 192.168.43.{1..13}
phuong@Arch ~/kitchen/ips$ ls
192.168.43.1 192.168.43.12 192.168.43.3 192.168.43.6 192.168.43.9
192.168.43.10 192.168.43.13 192.168.43.4 192.168.43.7
192.168.43.11 192.168.43.2 192.168.43.5 192.168.43.8
正如你可能猜到的那样,大括号展开也可以嵌套。
phuong@Arch ~/kitchen/ips$ echo A{a{1,2},b{9,10}}
Aa1 Aa2 Ab9 Ab10
命令替换
除了上述扩展机制之外,命令替换可能是你比较熟悉的一种,因为它在脚本编写中被广泛使用。命令替换允许我们将命令的输出用作扩展。例如:
phuong@Arch ~/kitchen$ ls -l $(which go)
lrwxrwxrwx 1 root root 18 Nov 2 17:25 /usr/bin/go -> /usr/lib/go/bin/go
这里还有另一个例子
phuong@Arch ~$ echo Hello $(w=world; echo $w)
Hello world
参数替换
这种替换方式在脚本编写中比在命令行中更常用。例如,如果你想打印 USER 变量,只需在变量名前加上美元符号($)即可。
phuong@Arch ~$ echo $USER
phuong
请注意,如果您输错了变量名,扩展仍然会进行,但结果会是空字符串。
为了简洁起见,我不会赘述细节,因为这种强大的机制值得另写一篇文章来详细介绍。本文旨在介绍一些常用的扩展机制以及如何控制它们。
引用
我们已经见识了 Shell 扩展功能的强大之处,正因如此,如果我们不懂得如何控制它,它可能会导致一些意想不到的行为。
现在,我们来到了有趣的部分。
请看以下两个例子:
例 1:
phuong@Arch ~$ echo Hello I am a string
Hello I am a string
例2:
phuong@Arch ~$ echo It costs me $200
It costs me 00
在第一个例子中,`splitting` 函数会从`echo` 命令的参数列表中删除多余的空格。在第二个例子中,` $2`被参数扩展机制展开,这是有意为之,但问题在于变量 `$2` 未定义,因此 Shell 会抑制意外的扩展,最终导致了现在的这种行为。我们来找出解决方法。
双引号
如果将特殊字符放在双引号中,Shell 将失去这些特殊含义,但有 3 个例外,即美元符号、反斜杠和反引号。
这在特定情况下非常有用。举个例子,假设你的朋友发给你一个作业文件,文件名是“作业第一部分.doc ”。嗯,这可能会让你抓狂。
这便导致了以下情况。
phuong@Arch ~/Downloads$ cat homework part1.doc
cat: homework: No such file or directory
cat: part1.doc: No such file or directory
双引号能帮到你。
phuong@Arch ~/Downloads$ mv "homework part1.doc" homework_part1.doc
phuong@Arch ~/Downloads$ cat homework_part1.doc
This is my stupid homework part, now is your turn
添加双引号后,我们的命令就包含一个命令和一个参数。这样就能保留多余的空格,因为上面提到的世界分割只对多个参数有效。如果所有内容都用双引号括起来,它的行为就如同 echo 命令后面只有一个参数一样。
phuong@Arch ~/Downloads$ echo Hello I am a string
Hello I am a string
phuong@Arch ~/Downloads$ echo "Hello I am a string"
Hello I am a string
单引号
如果您只需要屏蔽所有特殊字符,只需使用单引号即可。
phuong@Arch ~/Downloads$ echo 'cat ~/Download/.doc {a,b} $(echo bar) $((3+2)) $USER'
cat ~/Download/.doc {a,b} $(echo bar) $((3+2)) $USER
转义字符
有时我们只想引用单个字符,只需在该字符前加上反斜杠\即可。
phuong@Arch ~/Downloads$ echo "You own me $30"
You own me 0
phuong@Arch ~/Downloads$ echo "You own me \$30"
You own me $30
选择性地阻止文件名扩展是很常见的做法。
结论
总而言之,Unix Shell 的扩展机制非常强大。如果我们知道如何驾驭它,我相信这些特性肯定能提高您的工作效率。如果您有任何疑问,欢迎在下方留言,期待您的反馈。
感谢您抽出时间。
参考
- Linux 命令行 - William E. Shotts JR.

