Shell 进阶与命令行工具
在 lec1 中我们已经熟悉了 Linux 的基本操作和一些 shell 的基础语法。在这一节中,我们将继续学习 shell 的进阶用法和一些常用的命令行工具。
Shell 进阶语法
函数
我们今天要接触的第一个话题就是 shell 函数。你当然可以像其他语言一样定义函数,然后在需要的时候调用它。
试试用函数实现 A+B 吧!
| function a_plus_b() {
echo $(($1 + $2))
}
a_plus_b 1 2 # 输出 3
|
或者,你也可以省略 function
关键字,它们的效果是一样的。
| a_plus_b() {
echo $(($1 + $2))
}
a_plus_b 4 5 # 输出 9
|
注意到了吗,你可以像使用任何正常命令一样使用函数。函数的参数就是 $1
$2
$3
...。试试在函数内部 $@
$#
$0
这些变量的值吧!
表达式展开
表达式展开是一个更加庞大和复杂的话题——例如,解析一条 shell 命令要经历七种展开,你都知道么?
- brace expansion
- tilde expansion
- parameter and variable expansion
- command substitution
- arithmetic expansion
- word splitting
- filename expansion
在这里把这些展开全部介绍一遍是不现实的,我们只会介绍一些常用的语法,你可以在 GNU bash 文档 查看完整的介绍。
所以字面上来说,其实我们在之前介绍过的变量展开也是一种表达式展开。这些基础的展开方式也会伴随我们使用 shell 的一生。在看一些好玩的东西之前,我们先来看一些别的东西。我们需要这样一个函数,方便观察一些东西。
| show_args() {
echo "[$#] $@"
}
|
函数 show_args
做的事应当很好理解,它会输出传入的参数个数和参数本身。我们来看看这个函数的输出。
| user@debian:~$ show_args hello world
[2] hello world
user@debian:~$ show_args "hello world"
[1] hello world
user@debian:~$ show_args hello\ world
[1] hello world
|
当然,这些似乎都是我们已经知道的东西。但是下面这样呢:
| user@debian:~$ show_args he"ll"o "w"o'r'l"d"
[2] hello world
user@debian:~$ show_args hello" "world
[1] hello world
user@debian:~$ show_args hell"o w"orld
[1] hello world
|
没错,在 shell 中,引号实际上也可以出现在(几乎)任何地方。上面的 hello" "world
中将空格用引号括起来,并且引号前后都没有空格,所以这整个字符串是一个参数。在第三条命令中也是一样的道理。这实际上是非常有用的功能,例如平时在操作路径时,如果路径中有空格,我们可以用引号括起来,可能像这样:
| # 这些命令都是等价的
cd ~/Library/"Application Support"
cd ~/Library/Application\ Support
cd ~"/Library/Application Support" # 注意 ~ 不能放在引号内
cd ~/Library/Application" "Support
|
我们在 lec1 中研究过,无法在单引号字符串中使用 \'
来转义单引号。对于这个问题,我们可以通过拆分字符串来解决。在下面这个例子中,单引号 \'
位于左右两部分引号内容的中间,它自身不包含在单引号字符串中。
| user@debian:~$ show_args 'It'\''s MyGO!!!!!'
[1] It's MyGO!!!!!
|
在有了 show_args
函数和知道引号的用法之后,我们来看看花括号展开吧!
花括号展开
| user@debian:~$ show_args {1..5}
[5] 1 2 3 4 5
user@debian:~$ show_args {z..a}
[26] 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
user@debian:~$ show_args i{08..18..2}n
[6] i08n i10n i12n i14n i16n i18n
user@debian:~$ show_args {,s,t,k,m,h}{a,e,i,o,u}
[30] a e i o u sa se si so su ta te ti to tu ka ke ki ko ku ma me mi mo mu ha he hi ho hu
user@debian:~$ show_args c++{98,03,1{1,4,7,{x..z}},2{0..6..3}}
[11] c++98 c++03 c++11 c++14 c++17 c++1x c++1y c++1z c++20 c++23 c++26
|
花括号展开是一种非常有用的展开方式,它可以帮助我们快速生成一些特定格式的字符串。你也可以自己尝试一下,看看你能不能生成一些有趣的字符串。
有没有其他的使用场景呢?当然是有的。例如,你想要创建一些文件,但是文件名都是类似的,只有一部分不同。你可以这样:
| # 创建 file1.txt 到 file10.txt
touch file{1..10}.txt
# 创建 2020 年到 2024 年的 1 月到 12 月的目录
mkdir -p {2020..2024}/{01..12}
# 将 .zshrc 和 .vimrc 复制到 my-server
scp .{zsh,vim}rc my-server:
# 将 ./my_binary 移动到 /usr/local/bin/my_binary
mv {.,/usr/local/bin}/my_binary
|
命令替换
命令替换是另一种非常有用的展开方式。它可以帮助我们将一个命令的输出作为 shell 命令的一部分。例如:
| user@debian:~$ show_args $(echo hello world)
[2] hello world
user@debian:~$ show_args $(show_args hello world)
[3] [2] hello world
user@debian:~$ show_args "$(show_args "hello world")"
[1] [1] hello world
|
上面这三条命令经过命令替换后,实际上成为:
| show_args hello world
show_args [2] hello world
show_args "[1] hello world"
|
这有什么用呢?一个应用场景是,你想要查看某个可执行文件的信息:
| user@debian:~$ which ls echo
/usr/bin/ls
/usr/bin/echo
user@debian:~$ file $(which ls echo)
/usr/bin/ls: ELF 64-bit LSB pie executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, BuildID[sha1]=9f127c37a4c459cf01639f6ded2fcf11a49d3da9, for GNU/Linux 3.7.0, stripped
/usr/bin/echo: ELF 64-bit LSB pie executable, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, BuildID[sha1]=9be5659ffc854a3b33bfacc1523a40ab2760cc78, for GNU/Linux 3.7.0, stripped
user@debian:~$ ls -al $(which ls file which echo)
-rwxr-xr-x 1 root root 68408 Sep 20 2022 /usr/bin/echo
-rwxr-xr-x 1 root root 67920 Jan 29 2023 /usr/bin/file
-rwxr-xr-x 1 root root 200440 Sep 20 2022 /usr/bin/ls
lrwxrwxrwx 1 root root 23 Jul 29 2023 /usr/bin/which -> /etc/alternatives/which
|
别忘了,我们可以用 show_args
来观察命令替换的结果。
文件名展开
文件名展开是也许是最符合我们直觉的展开方式,它可以快速匹配文件名。例如我们想要查看当前目录下所有的 .txt
文件:
| user@debian:~$ ls *.txt
file10.txt file1.txt file2.txt file3.txt file4.txt file5.txt file6.txt file7.txt file8.txt file9.txt
|
或是删除 file1.txt
到 file9.txt
:
| user@debian:~$ rm file?.txt
user@debian:~$ ls *.txt
file10.txt
|
使用时要小心!如果有时候你不确定会展开成什么,可以先用 echo
命令来查看展开的结果。单个 *
会展开为当前目录下的所有非隐藏文件。如果你想要展开隐藏文件,可以使用 .*
。
glob
文件名展开的规则叫做 glob,它是一种简单的正则表达式。重要的是,实际上 glob 的规则是可以自定义的:你可以使用 shopt
来查看和修改 glob 的行为。更详细的介绍请查看 GNU bash 的文档。
变量
数组与关联数组
环境变量
子 shell
shebang
重定向
控制结构
条件判断
test
命令
循环
选择
命令行工具
grep
find
sed
cut
xargs