翼度科技»论坛 云主机 LINUX 查看内容

主题 2 Shell工具和脚本

6

主题

6

帖子

18

积分

新手上路

Rank: 1

积分
18
主题 2 Shell工具和脚本

Shell 工具和脚本 · the missing semester of your cs education (missing-semester-cn.github.io)
Shell脚本

shell 脚本是一种更加复杂度的工具。

  • 定义变量
在bash中为变量赋值的语法是foo=bar,意为定义变量foo,foo的值为bar。访问变量使用$变量名
  1. [lighthouse@VM-8-17-centos tools]$ foo=bar
  2. [lighthouse@VM-8-17-centos tools]$ echo "$foo"
  3. bar
复制代码
需要注意的是,Shell中使用空格作为分隔参数的保留字符。
如果将上诉赋值语句写为foo = bar,将不起作用。事实上,这样写并没有将bar赋给foo,而是用=和bar作为参数调用foo程序。因为这样Shell会认为你正在执行一个名为foo的命令。
  1. [lighthouse@VM-8-17-centos tools]$ foo = bar
  2. -bash: foo: command not found
复制代码
你需要特别注意这类问题,比如如果有带空格的文件名,你需要使用引号将其括起来。

  • 在bash中处理字符串
有两种定义字符串的方法,可以使用双引号定义字符串,也可以使用单引号定义字符串。
  1. [lighthouse@VM-8-17-centos tools]$ echo "Hello"
  2. Hello
  3. [lighthouse@VM-8-17-centos tools]$ echo 'Hello'
  4. Hello
复制代码
Bash中的字符串通过' 和 "分隔符来定义,但是它们的含义并不相同。
以'定义的字符串为原义字符串,其中的变量不会被转义,而 "定义的字符串会将变量值进行替换。
例如:
  1. [lighthouse@VM-8-17-centos tools]$ echo "Value is $foo"
  2. Value is bar
  3. [lighthouse@VM-8-17-centos tools]$ echo 'Value is $foo'
  4. Value is $foo
复制代码

  • 定义函数
和其他大多数的编程语言一样,bash也支持if, case, while 和 for 这些控制流关键字。同样地, bash 也支持函数,它可以接受参数并基于参数进行操作。
下面这个函数是一个例子,它会创建一个文件夹并使用cd进入该文件夹。
  1. [lighthouse@VM-8-17-centos tools]$ cat mcd.sh
  2. mcd(){
  3.         mkdir -p "$1"
  4.         cd "$1"
  5. }
复制代码
这里 $1 是脚本的第一个参数的意思
source 脚本名,这将会在Shell中加载脚本并运行。
  1. [lighthouse@VM-8-17-centos tools]$ source mcd.sh
  2. [lighthouse@VM-8-17-centos tools]$ mcd test
  3. [lighthouse@VM-8-17-centos test]$
复制代码
如上,在执行了source mcd.sh之后,看似无事发生,但实际上Shel中已经定义了mcd函数。我们给mcd传递一个参数test,这个参数被用于作为创建的目录名(即$1),然后Shell自动切换到了test目录里。整个过程就是,我们创建了文件夹并进入其中。

  • 保留字
在bash中,许多$开头的东西一般都是被保留的(指留作特定用途)
$1 是脚本的第一个参数的意思。与其他脚本语言不同的是,bash使用了很多特殊的变量来表示参数、错误代码和相关变量。下面列举其中一些变量,更完整的列表可以参考 这里
形式释义$0脚本名$1~$9脚本的参数, $1 是第一个参数,依此类推$@所有参数$#参数个数$?前一个命令的返回值$$当前脚本的进程识别码!!完整的上一条命令,包括参数。常见应用:当你因为权限不足执行命令失败时,可以使用 sudo !!再尝试一次。$_上一条命令的最后一个参数,如果你正在使用的是交互式 shell,你可以通过按下 Esc 之后键入 . 来获取这个值。有一些保留字可以直接在Shell中使用,例如$?可以获取上一条命令的错误代码(返回值),再比如$_会返回上一条命令的最后一个参数。
例如:
  1. [lighthouse@VM-8-17-centos tools]$ mkdir test
  2. [lighthouse@VM-8-17-centos tools]$ cd $_
  3. [lighthouse@VM-8-17-centos test]$
复制代码
如上,我们无需在写一次test,使用$_访问该参数,它就会被替换成test,现在我们进入到test目录中了。
这样的例子有很多,再例如!!,它返回完整的上一条命令,包括参数。常见应用:当你因为权限不足执行命令失败时,可以使用 sudo !!再尝试一次。
  1. [lighthouse@VM-8-17-centos tools]$ mkdir /mnt/new
  2. mkdir: cannot create directory ‘/mnt/new’: Permission denied
  3. [lighthouse@VM-8-17-centos tools]$ sudo !!
  4. sudo mkdir /mnt/new
  5. [lighthouse@VM-8-17-centos tools]$ rmdir /mnt/new
  6. rmdir: failed to remove '/mnt/new': Permission denied
  7. [lighthouse@VM-8-17-centos tools]$ sudo !!
  8. sudo rmdir /mnt/new
  9. [lighthouse@VM-8-17-centos tools]$
复制代码

  • 标准错误流
如果你的程序出错了,你想输出错误但不想污染标准输出,那么你可以写进这个流。

  • 错误代码
还有一种叫做错误代码$?(error code)的东西,是一种告诉你整个运行过程结果如何的方式。
  1. [lighthouse@VM-8-17-centos tools]$ echo "Hello"
  2. Hello
  3. [lighthouse@VM-8-17-centos tools]$ echo $?
  4. 0
复制代码
这里显示echo "Hello" 运行的错误代码为0,0是因为一切正常,没有出现问题。
这种退出码和如C语言里代表的意思一样。
0代表一切正常,没有出现错误。
  1. [lighthouse@VM-8-17-centos tools]$ grep foobar mcd.sh
  2. [lighthouse@VM-8-17-centos tools]$ echo $?
  3. 1
复制代码
如上,我们尝试着在mcd.sh脚本中查找foobar字符串,而它不存在,所以grep什么都没输出。但是通过反馈一个1的错误代码,它让我们知道这件事没有成功。
此外,true的错误代码始终是0;false的错误代码则是1。
  1. [lighthouse@VM-8-17-centos tools]$ true
  2. [lighthouse@VM-8-17-centos tools]$ echo $?
  3. 0
  4. [lighthouse@VM-8-17-centos tools]$ false
  5. [lighthouse@VM-8-17-centos tools]$ echo $?
  6. 1
复制代码

  • 逻辑运算符
下面bash要做的是执行第一个命令,如果第一个命令失败,再去执行第二个(短路运算法则)。因为它尝试做一个逻辑或,如果第一个命令没有0错误码,就会去执行第二个命令
  1. [lighthouse@VM-8-17-centos tools]$ false || echo "Oops fail"
  2. Oops fail
复制代码
相似地,如果我们把false换成true,那么将不会执行第二个命令,因为第一个命令已经返回一个0错误码了,第二个命令将会被短路。
  1. [lighthouse@VM-8-17-centos tools]$ true || echo "Oops fail"
  2. [lighthouse@VM-8-17-centos tools]$
复制代码
相似的,我们使用与运算符&&,它仅当第一个命令执行无错误时,才会执行第二个部分。如果第一个命令失败,那么第二个命令就不会被执行。
  1. [lighthouse@VM-8-17-centos tools]$ true && echo "Things went well"
  2. Things went well
  3. [lighthouse@VM-8-17-centos tools]$ false && echo "This will not print"
  4. [lighthouse@VM-8-17-centos tools]$
复制代码
使用;号连接的代码,无论你执行什么,都可以通过。在同一行使用分号来连接命令,如下,它始终会被打印出来。
  1. [lighthouse@VM-8-17-centos tools]$ false ; echo "This will always print"
  2. This will always print
复制代码

  • 把命令的输出存到变量里
这里我们获取pwd命令的输出,它会打印出我们当前的工作路径,然后把其存入foo变量中。然后我们询问变量foo的值,我们就可以看到这个字符串
  1. [lighthouse@VM-8-17-centos tools]$ foo=$(pwd)
  2. [lighthouse@VM-8-17-centos tools]$ echo $foo
  3. /home/lighthouse/missing-semester/tools
复制代码
更广泛地来说,我们可以通过一个叫做命令替换的东西,把它放进任意字符串中。并且因为我们使用的不是单引号,所以这串东西会被展开。
  1. [lighthouse@VM-8-17-centos tools]$ echo "We are in $(pwd)"
  2. We are in /home/lighthouse/missing-semester/tools
复制代码

  • 过程替换
另一个比较好用知名度更低的东西叫做过程替换。和之前的命令替换是类似的,例如
  1. [lighthouse@VM-8-17-centos tools]$ cat <(ls) <(ls ..)
  2. mcd.sh
  3. test
  4. tools
复制代码
第三行:有一个$(date)的参数,date打印出当前的时间。
第五行:$0代表着当前运行的脚本的名称,$#代表给定的参数个数,$$是这个命令的进程ID,一般缩写为PID。
第七行:$@可以展开成所有参数,比如有三个参数,你可以键入$1 $2 $3,如果你不知道有多少个参数,也可以直接键入$@。这里我们通过这种方式将所有参数放在这里,然后这些参数被传给for循环,for循环会创建一个file变量,依次地用这些参数赋值给file变量。
第八行:我们运行grep命令,它会在一堆文件里搜索一个子串。这里我们在文件里搜索字符串foobar,文件变量file将会展开为赋给它的值。
之前说过,如果我们在意程序的输出的话,我们可以把它重定向到某处(比如到一个文件里面保存下来,或者连接组合)。但有时候情况恰恰相反,例如有时候我们只想知道某个脚本的错误代码是什么,例如这里想知道grep能不能成功查找。我们并不在意程序的运行结果,因此我们甚至能直接扔掉整个输出,包括标准输出和标准错误流。这里我们做的就是把两个输出重定向到/dev/null,/dev/null是UNIX系统的一种特殊设备,输入到它的内容会被丢弃(就是说你可以随意乱写乱画,然后所有的内容都会被丢掉)。
这里的>代表重定向输出流,2>代表重定向标准错误流(因为这两个流是分立的,所以你要告诉bash去操作哪一个)。
所以这里我们执行命令,去检查文件有没有foobar字符串,如果有的话,返回一个0错误代码,如果没有返回一个非0错误代码。
第十一行:我们获取前一个命令的错误代码($?),然后是一个比较运算符-ne(代表不等于Non Equal)
其他编程序语言中有像=和≠,bash里有很多预设的比较运算(可以使用命令man test查看),这主要是为了你用Shell的时候,有很多东西要去测试。比如我们现在正在对比两个数,看它们是否相同。
如果文件中没有foobar,前一个命令将会返回一个非零错误代码。
第十二行:我们将会如果前一个命令返回一个非0错误代码,我们将会输出一句话File xxx does not have any foobar, adding one
第十三行:使用>>往对应文件中追加一行注释# foobar
现在我们来运行这个脚本,当前目录下有一些文件,我们将这些文件作为参数传给example.sh,检查是否有foobar。
  1. #!/bin/bash
  2.   
  3. echo "Start program at $(date)" # Date will be substituted
  4. echo "Running program $0 with $# arguments with pid $$"
  5. for file in "$@";do
  6.         grep foobar "$file" > /dev/null 2> /dev/null
  7.         # When pattern is not found,grep has exit status
  8.         # We redirect STDOUT and STDERR to a null register ..
  9.         if [[ "$?" -ne 0 ]]; then
  10.                 echo "File $file does not have any foobar, adding one"
  11.                 echo "# foobar" >> "$file"
  12.         fi      
  13. done
复制代码
我们在文件hello.txt和mcd.sh中没有找到foobar字符串,因此脚本分别给这两个文件添加了一个# foobar 注释
  1. [lighthouse@VM-8-17-centos tools]$ ls
  2. example.sh  hello.txt  mcd.sh
  3. [lighthouse@VM-8-17-centos tools]$ ./example.sh hello.txt mcd.sh
  4. Start program at Sun Dec 25 23:06:13 CST 2022
  5. Running program ./example.sh with 2 arguments with pid 2570038
  6. File hello.txt does not have any foobar, adding one
  7. File mcd.sh does not have any foobar, adding one
复制代码

  • 通配符
如果我们不想一个一个查找文件,可以使用通配符来进行匹配。
比如这里*匹配任意字符,这里将会显示出所有含有任意字符,并以.sh结尾的文件
  1. [lighthouse@VM-8-17-centos tools]$ cat hello.txt
  2. hello,this is a txt file
  3. # foobar
  4. [lighthouse@VM-8-17-centos tools]$ cat mcd.sh
  5. mcd(){
  6.         mkdir -p "$1"
  7.         cd "$1"
  8. }
  9. # foobar
复制代码
现在如果我只想找有一个而不是两个特定字符的项,可以使用?,?匹配一个字符
  1. [lighthouse@VM-8-17-centos tools]$ ls
  2. example.sh  hello.txt  image.png  mcd.sh  project1  project2  test
  3. [lighthouse@VM-8-17-centos tools]$ ls *.sh
  4. example.sh  mcd.sh
复制代码
现在我们得到了匹配的目录project1和project2
src是匹配的目录下的子项
总而言之,通配符非常强大,你也可以组合它们。
一个常用模式是花括号{}。
比如目录下有一个image.png图片,我们想转变该图像的格式,一般的做法是convert image.png image.jpg,但是你也可以键入convert image.{png,jpg},它会展开成上面的那行。
又如:
  1. [lighthouse@VM-8-17-centos tools]$ ls
  2. example.sh  hello.txt  image.png  mcd.sh  project1  project2  project42  test
  3. [lighthouse@VM-8-17-centos tools]$ ls project?
  4. project1:
  5. src
  6. project2:
  7. src
复制代码
如上所述,我们可以touch一串foo,所有的foo都会被展开。
你也可以进行多层操作,建立笛卡尔系:
[code][lighthouse@VM-8-17-centos tools]$ cat

举报 回复 使用道具