探索 shell 的工作方式

作者: 错过了过去 | 来源:发表于2019-06-27 18:18 被阅读13次

    通过前面的内容学习,你一定能在 shell 中干很多事情了,今天我们就一起再学习一些 shell 的工作方式,让我们更好的理解 shell。

    1. 通配符展开

    我们在 shell 中输入命令的时候,shell 不是马上就去执行相应的操作,而是要对我们的输入先做一些预处理,预处理后的输入才是 shell 真正执行的。

    20190328153446187_1554492313.png

    在上图中,我们使用 echo 命令列举了两个例子,可以看到 echo this is a test 这个例子只是在 shell 终端回显了 this is a test 这个字符串,但是当我们执行 echo * 的时候为什么回显的不是 * 呢?这就是 shell 预处理后的效果,* 是 shell 语法中的通配符,在这个例子中,* 代表的是当前目录下的所有文件和目录的名字(以 . 开头的文件或者目录是隐藏的,所以这里不包括它们)。

    使用 shell 的通配符,我们可以筛选我们需要显示的内容,下面是几个例子:

    20190328154446981_381862854.png

    在上图中,我们列举了几个通过 shell 通配符来筛选我们需要的内容的例子。

    • echo D* :打印出以 D 开头的文件或者目录名
    • echo *s :打印出以 s 结尾的文件或者目录名
    • echo [[:upper:]]* :打印出以大写字母开头的文件或者目录名
    • echo [[:lower:]]* :打印出以小写字母开头的文件或者目录名

    注意一点: echo * 是不会显示隐藏目录或者文件的,其他的命令,比如 ls,如果不加相应的参数,以 . 开头的文件或者目录也是隐藏的。想想会不会有问题呢?请看下面的例子:

    • ls .* :这个命令展开后,会有如下自命令:

      • ls .
      • ls ..:如果 .. 目录不是 /,那上级目录就还有 .. 目录,循环了,本来我们只想查看当前目录以 . 开头的文件或和目录,结果一直递归到 / 目录去了
      • 解决办法:
        • ls -d .* :-d 参数表示不显示子目录
        • ls -d .[!.]?*:这样可以过滤掉 ... 两个目录
    • 再举一个包含缓冲区漏洞的例子,echo 这个命令是有缓冲区漏洞的,所以不明确它后面跟的内容是什么样的效果,千万不要随便用,否则你会哭的。比如你刚写好的作业,名字叫做:homework.txt,这时候你如果执行:echo home*;rm -rf h*,本来你以为是在终端回显 home*;rm -rf h* 这个字符串,结果你可以试试。你会发现你的 homework.txt 文件再也找不到了,而且所有以 h 开头的文件都不见了。

    • ~ 的展开,shell 收到 ~ 的时候会自动将其展开为 /home/用户名 的形式。

    2. 算术表达式展开

    没有想到吧,我们可以把 shell 当做计算器来使用,下面我们一起来看看吧。

    20190328203400145_986210520.png

    从上图的几个例子中可以看出,只有符合:$((expression)) 格式的才能按照算术表达式展开,其中 (expression) 是算数表达式,$() 是取 () 表达式的值。还需要注意的是算数表达式支持整数,但能执行很多不同的操作,这些操作如下:

    • +:加
    • -:减
    • *:乘
    • /:除(结果是取整)
    • %:取余
    • **:取幂

    算数表达式中的空格并不重要,shell 预处理的时候会去掉,还有上图中有个例子:echo $(($((5**2)) * 3)) 中有两个 $,其中内层的 $ 可以省略。

    3. 花括号展开

    20190328224313808_1540648834.png

    对于花括号的展开,在我们日常使用 Linux 系统的过程中是很有帮助的,一来看看上图中的例子吧,看看在平时的工作中什么时候会用到这种展开方式。

    1、 chaojun@ubuntu:~$ echo Front-{A,B,C}-Back

    A-Back Front-B-Back C-Back

    从这个例子中可以看出我们使用一条命令就打印除了三个不同的名字,这在 shell 脚本中需要创建多个有相同部分的文件或者目录时是非常有用的。

    2、 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

    这个例子也挺有意思的,我们打印了 26 个倒序的英文字母,如果换成 echo {A..Z} 则是打印的正序的。你不妨载试试:echo {0..20},哈哈,是不是很有意思。

    3、 echo a{A{1,2},B{3,4}}b

    aA1b aA2b aB3b aB4b

    这个例子主要是为了说明 {} 还可以嵌套,注意我标明的颜色。

    4、 创建保存照片的文件夹的例子

    chaojun@ubuntu:~$ mkdir Pics
    chaojun@ubuntu:~$ cd Pics/
    chaojun@ubuntu:~/Pics$ mkdir {2007..2009}-0{1..9} {2007..2009}-{10..12}
    chaojun@ubuntu:~/Pics$ ls
    2007-01  2007-04  2007-07  2007-10  2008-01  2008-04  2008-07  2008-10  2009-01  2009-04  2009-07  2009-10
    2007-02  2007-05  2007-08  2007-11  2008-02  2008-05  2008-08  2008-11  2009-02  2009-05  2009-08  2009-11
    2007-03  2007-06  2007-09  2007-12  2008-03  2008-06  2008-09  2008-12  2009-03  2009-06  2009-09  2009-12
    chaojun@ubuntu:~/Pics$
    

    这个例子在实际生活中也是很有用的,比如我们需要安装年份加月份的格式创建一系列文件夹来保存不同时间拍摄的照片,我们就可以先创建一个文件夹 mkdir Pics,然后将工作目录切换到刚才创建的目录 cd Pics/,然后使用 mkdir {2007..2009}-0{1..9} {2007..2009}-{10..12} 批量创建我们需要的目录。

    4. 参数展开

    在 Linux 系统中也有环境变量这一概念,我们可以使用 printenv 查看,然后我们在 shell 中使用 $ 取环境变量中的变量名就可以将其值展开了。

    20190329172732397_1274648659.png

    上图中的前 5 行是使用 printenv 打印出来的结果的后 5 行,然后接下来我使用 echo $变量名 来查看参数展开的效果。

    5. 命令替换

    20190329173136418_1834818477.png

    上图中展示了几个命令替换的例子,格式是:命令1 $命令2 ,其效果就是命令 2 的输出会当成命令 1 的输入。大家可以自己试试。同时第二个例子还加入了我们之前学过的管道符 |,是不是就更有实用意义了呢?

    6. 引用

    这里的引用就是说用 "" 双引号和 '' 单引号引着的内容在 shell 中的表现,我们通过如下例子来看看它们的特性。

    • echo this is a test
    • echo "this is a test"
    • echo The total is $100.00
    • ls -l two words.txt
    • ls -l "two words.txt"
    • echo "$USER $((2+2)) $(cal)"
    • echo $(cal)
    • echo "$(cal)"
    • echo text ~/*.txt {a,b} $(echo foo) $((2+2)) $USER
    • echo "text ~/*.txt {a,b} $(echo foo) $((2+2)) $USER"
    • echo 'text ~/*.txt {a,b} $(echo foo) $((2+2)) $USER'
    20190329175102371_1493346908.png

    你可以先自己执行上面的命令,看看加上单引号和双引号以及不加引号有什么区别,思考一下为什么?如果没想明白再看看我下面的讲解你就会明白了。

    • 如果我们没有加引号,那么多个连续的空格 shell 会只保留一个空格(第一个例子)
    • $后面的名字如果不是一个环境变量,shell 找不到它,那 shell 就会取名字的第一个字符替换为空格,后面剩下的字符回显到终端(第二个例子)
    • 如果文件名中有空格,不加引号,shell 是找不到它的(第三个例子和第四个例子)
    • 双引号中的参数展开、算术表达式展开和命令替换仍然有效(第五个例子和第七个例子,注意)
    • 在默认情况下,单词分割机制会在单词中寻找空格,制表符,和换行符,并把它们看作 单词之间的界定符。这意味着无引用的空格,制表符和换行符都不是文本的一部分, 它们只作为分隔符使用。由于它们把单词分为不同的参数,所以在上面的第一个例子中, 命令行包含一个带有四个不同参数的命令。如果我们加上双引号(第二个例子):单词分割被禁止,内嵌的空格也不会被当作界定符,它们成为参数的一部分。 一旦加上双引号,我们的命令行就包含一个带有一个参数的命令。也可以结合第七和第八两个例子看效果
    • 如果需要禁止所有的展开,我们要使用单引号。请结合最后三个例子看效果

    7. 转义字符

    有时候我们只想引用单个字符。我们可以在字符之前加上一个反斜杠,在这里叫做转义字符。 经常在双引号中使用转义字符,来有选择地阻止展开:

    20190329180508288_1218731794.png

    使用转义字符来消除文件名中一个字符的特殊含义,是很普遍的。例如,在文件名中可能使用 一些对于 shell 来说有特殊含义的字符。这些字符包括”$”, “!”, “ “等字符。在文件名 中包含特殊字符,你可以这样做:mv bad\&filename good_filename

    为了允许反斜杠字符出现,我们需要连续输入两个反斜杠 \\,下图是一个例子:

    20190329180720836_460790502.png

    随着我们继续学习 shell,你会发现使用展开和引用的频率逐渐多起来,所以能够很好的理解它们的工作方式很有意义。事实上,可以这样说,它们是学习 shell 的最重要的主题。 如果没有准确地理解展开模式,shell 总是神秘和混乱的源泉,并且 shell 潜在的能力也浪费掉了。

    对于 Linux 比较熟悉的朋友,看了本专栏 shell 相关的文章,相信你对 shell 又有了新的认识,对于新入门的朋友,希望你不要畏惧 Linux shell 这么多的特性记不住,只要你慢慢用,用多了自然就记住了,没有必要去死记硬背。

    欢迎关注知乎专栏:Linux 漫游之旅,欢迎关注微信公众号:Linux 漫游之旅,免费提供 CSDN 下载服务。

    相关文章

      网友评论

        本文标题:探索 shell 的工作方式

        本文链接:https://www.haomeiwen.com/subject/dkqdcctx.html