[MIT公开课(计算机教育中缺失的一课)]2.Shell工具与脚本

(该系列文章大部分内容来源于MIT课程笔记,加入了个人的理解、原笔记中没有的细节和其他的需要理解的内容,公开课地址:https://www.bilibili.com/video/BV14E411J7n2?p=1

上一讲:Overview+Shell笔记
下一讲:编辑器(Vim)



Shell 脚本

大多数shell都有自己的一套脚本语言,包括变量、控制流和自己的语法。shell脚本与其他脚本语言不同之处在于,shell脚本针对shell所从事的相关工作进行来优化。因此,创建命令流程(pipelines)、将结果保存到文件、从标准输入中读取输入,这些都是shell脚本中的原生操作,这让它比通用的脚本语言更易用。本节中,我们会专注于bash脚本,因为它最流行,应用更为广泛。

在bash中为变量赋值的语法是foo=bar,访问变量中存储的数值,其语法为 $foo。 需要注意的是,foo = bar (使用空格隔开)是不能正确工作的,因为解释器会调用程序foo 并将 = 和 bar作为参数。 总的来说,在shell脚本中使用空格会起到分割参数的作用,有时候可能会造成混淆,请务必多加检查。

Bash中的字符串通过’ 和 "分隔符来定义,但是它们的含义并不相同。以’定义的字符串为原义字符串,其中的变量不会被转义,而 "定义的字符串会将变量值进行替换:

lilhoe@LilHoedeMacBook-Pro Downloads % hsj=ad
lilhoe@LilHoedeMacBook-Pro Downloads % echo $hsj
ad
lilhoe@LilHoedeMacBook-Pro Downloads % echo "$hsj"
ad
lilhoe@LilHoedeMacBook-Pro Downloads % echo '$hsj'
$hsj
lilhoe@LilHoedeMacBook-Pro Downloads % 

bash 也支持函数,它可以接受参数并基于参数进行操作。下面这个函数是一个例子,它会创建一个函数并使用cd进入该文件夹。首先输入vim adh.sh进入文件编程模式,点击i进入修改模式:在这里插入图片描述

mcd () {
    
    
    mkdir -p "$1"
    cd "$1"
}

在这里插入图片描述
这里 $1 是脚本到第一个参数。与其他脚本语言不同到是,bash使用了很多特殊到变量来表示参数、错误代码和相关变量。下面是列举来其中一些变量,更完整到列表可以参考 这里

$0 - 脚本名
$1 到 $9 - 脚本到参数。
$1 是第一个参数,依此类推。
$@ - 所有参数
$# - 参数个数
$? -前一个命令到返回值
$$ - 当前脚本到进程识别码
!! - 完整到上一条命令,包括参数。常见应用:当你因为权限不足执行命令失败时,可以使用sudo !!再尝试一次。
$_ - 上一条命令的最后一个参数。如果你正在使用的是交互式shell,你可以通过按下 Esc 之后键入 .来获取这个值。

最后键入:q!退出,若文件有修改键入:wq保存退出。

通过source adh.sh指令执行adh.sh文件,看似没有事情发生,但是adh文件已经被执行。输入mcd test会从工具目录转到测试目录。

命令通常使用 STDOUT来返回输出值,使用STDERR 来返回错误及错误码,便于脚本以更加友好到方式报告错误。 返回码或退出状态是脚本/命令之间交流执行状态到方式。返回值0表示正常执行,其他所有非0的返回值都表示有错误发生。

退出码可以搭配&& (与操作符) 和 || (或操作符)使用,用来进行条件判断,决定是否执行其他程序。同一行的多个命令可以用 ; 分隔。程序 true 的返回码永远是0,false 的返回码永远是1。

另一个常见的模式是以变量的形式获取一个命令的输出,这可以通过 命令替换 (command substitution)实现。

当您通过 $( CMD ) 这样的方式来执行CMD 这个命令时,然后它的输出结果会替换掉 $( CMD ) 。例如,如果执行 for file in $(ls) ,shell首先将调用ls ,然后遍历得到的这些返回值。

还有一个冷门的类似特性是 进程替换(process substitution), <( CMD ) 会执行 CMD 并将结果输出到一个临时文件中,并将 <( CMD ) 替换成临时文件名。这在我们希望返回值通过文件而不是STDIN传递时很有用。例如, diff <(ls foo) <(ls bar) 会显示文件夹 foo 和 bar 中文件的区别。

下面这个例子展示了一部分上面提到的特性。这段脚本会遍历我们提供的参数,使用grep 搜索字符串 foobar,如果没有找到,则将其作为注释追加到文件中。将如下文件保存到example.sh中:

#!/bin/bash

echo "Starting program at $(date)" # date会被替换成日期和时间

echo "Running program $0 with $# arguments with pid $$"

for file in $@; do
    grep foobar $file > /dev/null 2> /dev/null
    # 如果模式没有找到,则grep退出状态为 1
    # 我们将标准输出流和标准错误流重定向到Null,因为我们并不关心这些信息
    if [[ $? -ne 0 ]]; then
    # -ne for "not equal", for more details see "man test"
        echo "File $file does not have any foobar, adding one"
        echo "# foobar" >> "$file"
    fi
done

在bash中进行比较时,尽量使用双方括号 [[ ]] 而不是单方括号 [ ],这样会降低犯错的几率,尽管这样并不能兼容 sh。 更详细的说明参见这里

当执行脚本时,我们经常需要提供形式类似的参数。bash使我们可以轻松的实现这一操作,它可以基于文件扩展名展开表达式。这一技术被称为shell的 通配( globbing)

通配符 - 当你想要利用通配符进行匹配时,你可以分别使用 ? 和 * 来匹配一个或任意个字符。例如,对于文件foo, foo1, foo2, foo10 和 bar, rm foo?这条命令会删除foo1 和 foo2 ,而rm foo*则会删除除了bar之外的所有文件。
花括号{} - 当你有一系列的指令,其中包含一段公共子串时,可以用花括号来自动展开这些命令。这在批量移动或转换文件时非常方便。

convert image.{
    
    png,jpg}
# 会展开为
convert image.png image.jpg

cp /path/to/project/{
    
    foo,bar,baz}.sh /newpath
# 会展开为
cp /path/to/project/foo.sh /path/to/project/bar.sh /path/to/project/baz.sh /newpath

# 也可以结合通配使用
mv *{
    
    .py,.sh} folder
# 会移动所有 *.py 和 *.sh 文件

mkdir foo bar

# 下面命令会创建foo/a, foo/b, ... foo/h, bar/a, bar/b, ... bar/h这些文件(即笛卡尔积运算)

touch {
    
    foo,bar}/{
    
    a..h}
touch foo/x bar/y
# 显示foo和bar文件的不同 
diff <(ls foo) <(ls bar)
# 输出
# < x
# ---
# > y

(Mac系统中运行convert指令会报错,原因是Mac的convert封装在textutil中,textutil 是一个系统自带的,用于处理文稿的命令)

编写 bash 脚本有时候会很别扭和反直觉。例如 shellcheck这样的工具可以帮助你定位sh/bash脚本中的错误。

在Mac系统上,如果安装了Homebrew,可以直接在终端输入brew install shellcheck即可成功安装。下举例检测example.sh文件:

lilhoe@LilHoedeMacBook-Pro Downloads % shellcheck example.sh  

In example.sh line 6:
    grep foobar $file > /dev/null 2> /dev/null>
    ^-- SC1009: The mentioned syntax error was in this simple command.
                                              ^-- SC1073: Couldn't parse this redirection. Fix to allow more checks.
                                               ^-- SC1072:  Fix any mentioned problems and try again.

For more information:
  https://www.shellcheck.net/wiki/SC1072 --  Fix any mentioned problems and t...
  https://www.shellcheck.net/wiki/SC1073 -- Couldn't parse this redirection. ...
  https://www.shellcheck.net/wiki/SC1009 -- The mentioned syntax error was in...
lilhoe@LilHoedeMacBook-Pro Downloads % 

注意,脚本并不一定只有用bash写才能在终端里调用。比如说,这是一段Python脚本,作用是将输入的参数倒序输出:

vim test.py
#!/usr/local/bin/python
import sys
for arg in reversed(sys.argv[1:]):
    print(arg)

通过python的方式执行文件:

python test.py a b c
c
b
a

通过shell的方式执行文件:

在这里插入代码片

shell知道去用python解释器而不是shell命令来运行这段脚本,是因为脚本的开头第一行的shebang。

在shebang行中使用 env 命令是一种好的实践,它会利用环境变量中的程序来解析该脚本,这样就提高来您的脚本的可移植性。env 会利用我们第一节讲座中介绍过的PATH 环境变量来进行定位。 例如,使用了env的shebang看上去时这样的#!/usr/bin/env python

  • shell函数和脚本有如下一些不同点:
  1. 函数只能用与shell使用相同的语言,脚本可以使用任意语言。因此在脚本中包含 shebang 是很重要的。

  2. 函数仅在定义时被加载,脚本会在每次被执行时加载。这让函数的加载比脚本略快一些,但每次修改函数定义,都要重新加载一次。

  3. 函数会在当前的shell环境中执行,脚本会在单独的进程中执行。因此,函数可以对环境变量进行更改,比如改变当前工作目录,脚本则不行。脚本需要使用 export 将环境变量导出,并将值传递给环境变量。

  4. 与其他程序语言一样,函数可以提高代码模块性、代码复用性并创建清晰性的结构。shell脚本中往往也会包含它们自己的函数定义。


Shell工具

移动光标

按键 行动
Ctrl-a 移动光标到行首
Ctrl-e 移动光标到行尾。
Ctrl-f 光标前移一个字符;和右箭头作用一样。
Ctrl-b 光标后移一个字符;和左箭头作用一样。
Alt-f 光标前移一个字。
Alt-b 光标后移一个字。
Ctrl-l 清空屏幕,移动光标到左上角。clear 命令完成同样的工作。

修改文本

按键 行动
Ctrl-d 删除光标位置的字符。
Ctrl-t 光标位置的字符和光标前面的字符互换位置。
Alt-t 光标位置的字和其前面的字互换位置。
Alt-l 把从光标位置到字尾的字符转换成小写字母。
Alt-u 把从光标位置到字尾的字符转换成大写字母。

剪切和粘贴文本

按键 行动
Ctrl-k 剪切从光标位置到行尾的文本。
Ctrl-u 剪切从光标位置到行首的文本。
Alt-d 剪切从光标位置到词尾的文本。
Alt-Backspace 剪切从光标位置到词头的文本。如果光标在一个单词的开 头,剪切前一个单词。
Ctrl-y 把剪切环中的文本粘贴到光标位置。

自动补全命令

按键 行动
Alt-? 显示可能的自动补全列表。或者通过按两次 tab 键
Alt-* 插入所有可能的自动补全。(想要使用多个可能的匹配项)

查看命令如何使用

最常用的方法是为对应的命令行添加-h--help标记(bash中还可以使用help+command来查看,zsh中不可以)。在交互式的、基于字符处理的终端窗口中,一般也可以通过:help 命令或键入 ?来获取帮助。另外一个更详细的方法则是使用man命令。

man 所显示的参考手册,被分成几个章节,它们不仅仅包括用户命令,也包括系统管理员 命令、程序接口、文件格式等等。下表描绘了手册的布局:

章节 内容
1 用户命令
2 程序接口内核系统调用
3 C 库函数程序接口
4 特殊文件,比如说设备结点和驱动程序
5 文件格式
6 游戏娱乐,如屏幕保护程序
7 其他方面
8 系统管理员命令

有时候,我们需要查看参考手册的特定章节,从而找到我们需要的信息。如果我们要查找一 种文件格式,而同时它也是一个命令名时, 这种情况尤其正确。没有指定章节号,我们总是得 到第一个匹配项,可能在第一章节。我们这样使用 man 命令,来指定章节号:

man section search_term

例如:man passwd显示的是命令名passwd的手册:

在这里插入图片描述
man 5 passwd显示的是文件格式passwd的手册:
在这里插入图片描述

有时候手册内容太过详实,让我们难以在其中查找哪些最常用的标记和语法。 whatis 程序显示匹配特定关键字的手册页的名字和一行命令说明。另一个替代品TLDR pages 提供了一些案例,可以帮助您快速找到正确的选项。

(博主的Macbook上安装了tldr但是键入该命令没有反应,还希望有用的朋友看到了在评论区指点一下!)

查找文件

find

所有的类UNIX系统都包含一个名为find的工具,它是shell上用于查找文件的绝佳工具。find命令会递归地搜索符合条件的文件,例如:

# 查找所有名称为src的文件夹("."表示当前文件夹 -name 表示名字 -type 表示类型 d为文件夹
find . -name src -type d
# 查找所有文件夹路径中包含test的python文件
find . -path '**/test/**/*.py' -type f
# 查找前一天修改的所有文件(-mtime表示修改时间)
find . -mtime -1
# 查找所有大小在500k至10M的tar.gz文件
find . -size +500k -size -10M -name '*.tar.gz'

find后面的参数都叫做测试条件,常用的测试条件如下:

测试条件 描述
-cmin n 匹配内容或属性最后修改时间正好在 n 分钟之前的文件或 目录。指定少于 n 分钟之前,使用 -n,指定多于 n 分钟之 前,使用 +n。
-cnewer file 匹配内容或属性最后修改时间晚于 file 的文件或目录。
-ctime n 匹配内容和属性最后修改时间在 n*24 小时之前的文件和目录。
-empty 匹配空文件和目录。
-group name 匹配属于一个组的文件或目录。组可以用组名或组 ID 来表示。
-iname pattern 就像-name 测试条件,但是不区分大小写。
-inum n 匹配 inode 号是 n 的文件。这对于找到某个特殊 inode 的所有硬链接很有帮助。
-mmin n 匹配内容被修改于 n 分钟之前的文件或目录。
-mtime n 匹配的文件或目录的内容被修改于 n*24 小时之前。
-name pattern 用指定的通配符模式匹配的文件和目录。
-newer file 匹配内容晚于指定的文件的文件和目录。这在编写执行备份的 shell 脚本的时候很有帮。每次你制作一个备份,更新文件(比如说日志),然后使用 find 命令来判断哪些文件自从上一次更新之后被更改了。
-nouser 匹配不属于一个有效用户的文件和目录。这可以用来查找 属于被删除的帐户的文件或监测攻击行为。
-nogroup 匹配不属于一个有效的组的文件和目录。
-perm mode 匹配权限已经设置为指定的 mode 的文件或目录。mode 可以用八进制或符号表示法。
-samefile name 类似于-inum 测试条件。匹配和文件 name 享有同样 inode 号的文件。
-size n 匹配大小为 n 的文件
-type c 匹配文件类型是 c 的文件。
-user name 匹配属于某个用户的文件或目录。这个用户可以通过用户 名或用户 ID 来表示。

-type中的文件类型:

文件类型 描述
b 块特殊设备文件
c 字符特殊设备文件
d 目录
f 普通文件
l 符号链接

除了列出所寻找的文件之外,find还能对所有查找到的文件进行操作:

  1. 预定义的操作:
操作 描述
-delete 删除当前匹配的文件。
-ls 对匹配的文件执行等同的 ls -dils 命令。并将结果发送到标 准输出。
-print 把匹配文件的全路径名输送到标准输出。如果没有指定其 它操作,这是默认操作。
-quit 一旦找到一个匹配,退出。
  1. 用户定义的行为:
    除了预定义的行为之外,我们也可以调用任意的命令。传统方式是通过 -exec 行为:
-exec command {
    
    } ;

command 就是指一个命令的名字,{} 是当前路径名的符号表示,;是必要的分隔符表明命令的结束,例如:

# Delete all files with .tmp extension
find . -name '*.tmp' -exec rm {
    
    } \;
# Find all PNG files and convert them to JPG
find . -name '*.png' -exec convert {
    
    } {
    
    .}.jpg \;

我们也可以交互式地执行一个用户定义的行为。通过使用 -ok行为来代替 -exec,在执行每 个指定的命令之前,会提示用户:

lilhoe@LilHoedeMacBook-Pro: ~/Downloads
$ find ~ -type f -name 'foo*' -ok ls -l '{}' ';'
"ls -l /Users/lilhoe/Desktop/Python/教材电子资源1st/chapter_04/foods.py"? y
-rw-rw-r--@ 1 lilhoe  staff  252  8 10  2018 /Users/lilhoe/Desktop/Python/教材电子资源1st/chapter_04/foods.py
"ls -l /Users/lilhoe/Desktop/Python/教材电子资源2nd/chapter_04/foods.py"? 
...

我们还需要更好地方法表示测试条件之间的逻辑关系,操作符如下:

操作符 描述
-and 如果操作符两边的测试条件都是真,则匹配。可以简写为 -a。注意若没有使用操作符,则默认使用 -and。
-or 若操作符两边的任一个测试条件为真,则匹配。可以简写为 -o。
-not 若操作符后面的测试条件是假,则匹配。可以简写为一个感叹号(!)。
() 把测试条件和操作符组合起来形成更大的表达式。这用来控制逻辑计算的优先级。默认情况下,find 命令按照从左到右的顺序计算。经常有必要重写默认的求值顺序,以得到期望的结果。即使没有必要,有时候包括组合起来的字符,对提高命令的可读性是很有帮助的。注意因为圆括号字符对于 shell 来说有特殊含义,所以在命令行中使用它们的时候, 它们必须用引号引起来,才能作为实参传递给 find 命令。通常反斜杠字符被用来转义圆括号字符。

例如,如果我们需要确定是否一个目录中的所有的文件和子目录拥有安全权限,怎么办呢?我们可以查找权限不是 0600 的文件和权限不是 0700 的目录:

lilhoe@LilHoedeMacBook-Pro: ~/Downloads
$ find ~ \( -type f -not -perm 0600 \) -or \( -type d -not -perm 0700 \)|wc -l
  248247
fd

尽管 find用途广泛,它的语法却比较难以记忆。您当然可以使用alias设置别名来简化上述操作,但shell的哲学之一便是寻找(更好用的)替代方案。 记住,shell最好的特性就是您只是在调用程序,因此您只要找到合适的替代程序即可(甚至自己编写)。

例如, fd 就是一个更简单、更快速、更友好的程序,它可以用来作为find的替代品。它有很多不错的默认设置,例如输出着色、默认支持正则匹配、支持unicode并且我认为它的语法更符合直觉。以模式PATTERN 搜索的语法是fd PATTERN

locale

大多数人都认为 find 和 fd 已经很好用了,但是有的人可能向知道,我们是不可以可以有更高效的方法,例如不要每次都搜索文件而是通过编译索引或建立数据库的方式来实现更加快速地搜索。

这就要靠 locate 了。 locate 使用一个由 updatedb负责更新的数据库,在大多数系统中 updatedb 都会通过 cron每日更新。这便需要我们在速度和时效性之间作出权衡。而且,find 和类似的工具可以通过别的属性比如文件大小、修改时间或是权限来查找文件,locate则只能通过文件名。 here有一个更详细的对比。

locate程序会执行一次快速的路径名数据库搜索,并且输出每个与给定子字符串相匹配的路径名。例如,我们想要找到所有名字以“zip”开头的程序。因为我们正在查找程序,可以假定包含程序的目录以 “bin/” 结尾:

[me@linuxbox ~]$ locate bin/zip
/usr/bin/zip
/usr/bin/zipcloak
/usr/bin/zipgrep
/usr/bin/zipinfo
/usr/bin/zipnote
/usr/bin/zipsplit

如果搜索要求没有这么简单,locate 可以结合其它工具,比如说 grep 命令,来设计更加有趣的搜索:

[me@linuxbox ~]$ locate zip | grep bin /bin/bunzip2
/bin/bzip2
/bin/bzip2recover
/bin/gunzip 
/bin/gzip 
/usr/bin/funzip 
/usr/bin/gpg-zip 
/usr/bin/preunzip 
/usr/bin/prezip 
/usr/bin/prezip-bin 
/usr/bin/unzip 
/usr/bin/unzipsfx 
/usr/bin/zip 
/usr/bin/zipcloak 
/usr/bin/zipgrep 
/usr/bin/zipinfo 
/usr/bin/zipnote 
/usr/bin/zipsplit

查看文件(less)

less 程序允许你前后滚动文件:

less filename

less 程序最常使用的键盘命令:

命令 行为
Page UP or b 向上翻滚一页
Page Down or space 向下翻滚一页
UP Arrow 向上翻滚一行
Down Arrow 向下翻滚一行
G 移动到最后一行
1G or g 移动到开头一行
/charaters 向前查找指定的字符串
n 向前查找下一个出现的字符串,这个字符串是之前所指定查找的
h 显示帮助屏幕
q 退出 less 程序

查找代码

查找文件是很有用的技能,但是很多时候您的目标其实是查看文件的内容。一个最常见的场景是您希望查找具有某种模式的全部文件,并找它们的位置。

为了实现这一点,很多类UNIX的系统都提供了grep命令,它是用于对输入文本进行匹配的通用工具。grep有很多选项,这也使它成为一个非常全能的工具。其中我经常使用的有-C :获取查找结果的上下文(Context);-v将对结果进行反选(Invert),也就是输出不匹配的结果。举例来说, grep -C 5 会输出匹配结果前后五行。当需要搜索大量文件的时候,使用 -R 会递归地进入子目录并搜索所有的文本文件。

但是,我们有很多办法可以对grep -R进行改进,例如使其忽略.git文件夹,使用多CPU等等。因此也出现了很多它的替代品,包括 ack, agrg。我比较常用的是ripgrep (rg) ,因为它速度快,而且用法非常符合直觉。例子如下:

# 查找所有使用了 requests 库的文件
rg -t py 'import requests'
# 查找所有没有写 shebang 的文件(包含隐藏文件)
rg -u --files-without-match "^#!"
# 查找所有的foo字符串,并打印其之后的5行
rg foo -A 5
# 打印匹配的统计信息(匹配的行和文件的数量)
rg --stats PATTERN

查找 shell 命令(history)

history 命令允许您以程序员的方式来访问shell中输入的历史命令。这个命令会在标准输出中打印shell中的里面命令。如果我们要搜索历史记录,则可以利用管道将输出结果传递给 grep 进行模式搜索。 history | grep find 会打印包含find子串的命令:

lilhoe@LilHoedeMacBook-Pro ~ % history|grep man
  851  man locales
  852  man locale
  855  man
  856  man rg
  858  man rrg
  859  man rg
  862  man ag

对于大多数的shell来说,您可以使用 Ctrl+R 对命令历史记录进行回溯搜索。敲 Ctrl+R 后您可以输入子串来进行匹配,查找历史命令行。反复按下就会在所有搜索结果中循环。在 zsh中,使用方向键上或下也可以完成这项工作。

Ctrl+R 可以配合 fzf 使用。fzf 是一个通用对模糊查找工具,它可以和很多命令一起使用。这里我们可以对历史命令进行模糊查找并将结果以赏心悦目的格式输出。

另外一个和历史命令相关的技巧我喜欢称之为基于历史的自动补全。 这一特性最初是由 fish shell 创建的,它可以根据您最近使用过的开头相同的命令,动态地对当前对shell命令进行补全。这一功能在 zsh 中也可以使用,它可以极大对提高用户体验。

最后,有一点值得注意,输入命令时,如果您在命令的开头加上一个空格,它就不会被加进shell记录中。当你输入包含密码或是其他敏感信息的命令时会用到这一特性。如果你不小心忘了在前面加空格,可以通过编辑。bash_history.zhistory 来手动地从历史记录中移除那一项。


文件夹导航

如何才能高效地在目录 间随意切换呢?有很多简便的方法可以做到,比如设置alias,使用 ln -s创建符号连接等。

用fasd可以查找最常用和/或最近使用的文件和目录。Fasd 基于 frecency对文件和文件排序,也就是说它会同时针对频率(frequency )和时效( recency)进行排序。

最直接对用法是自动跳转 (autojump),对于经常访问的目录,在目录名子串前加入一个命令 z 就可以快速切换命令到该目录。例如, 如果您经常访问/home/user/files/cool_project 目录,那么可以直接使用 z cool 跳转到该目录。

还有一些更复杂的工具可以用来概览目录结构,例如 tree, broot 或更加完整对文件管理器,例如 nnnranger

  • tree使用举例:
# 递归列出当前文件夹的所有内容
ls -r

输出:
在这里插入图片描述
可见这种列表的形式非常不清晰,没有显示层次结构。再输入tree命令,输出结果将变成树状图:

在这里插入图片描述

  • broot使用举例:
    在这里插入图片描述

当我继续键入内容,该树状图会根据键入的内容匹配出符合的节点动态修改,如我输入rr:
在这里插入图片描述


课后练习

  1. 阅读 man ls ,然后使用ls 命令进行如下操作:

所有文件(包括隐藏文件)
文件打印以人类可以理解的格式输出 (例如,使用454M 而不是 454279954)
文件以最近访问顺序排序
以彩色文本显示输出结果

典型输出如下:

 -rw-r--r--   1 user group 1.1M Jan 14 09:53 baz
 drwxr-xr-x   5 user group  160 Jan 14 09:53 .
 -rw-r--r--   1 user group  514 Jan 14 06:42 bar
 -rw-r--r--   1 user group 106M Jan 13 12:12 foo
 drwx------+ 47 user group 1.5K Jan 12 18:08 ..

解答:

LilHoedeMacBook-Pro :: ~/Downloads » ls -ahuGlF!
# -a 打印包括隐藏文件
# -h human-readble
# -l 输出用户和组名而不是他们俩的id(-n)
# -u 按照最近访问顺序输出(而不是最近修改顺序-t)
# -G 彩色输出
# -F 在文件夹后面加"\",可执行文件后加"*"...

在这里插入图片描述

  1. 编写两个bash函数 marco 和 polo 执行下面的操作。 每当你执行 marco 时,当前的工作目录应当以某种形式保存,当执行 polo 时,无论现在处在什么目录下,都应当 cd 回到当时执行 marco 的目录。 为了方便debug,你可以把代码写在单独的文件 marco.sh 中,并通过 source marco.sh命令,(重新)加载函数。

  2. 假设您有一个命令,它很少出错。因此为了在出错时能够对其进行调试,需要花费大量的时间重现错误并捕获输出。 编写一段bash脚本,运行如下的脚本直到它出错,将它的标准输出和标准错误流记录到文件,并在最后输出所有内容。 加分项:报告脚本在失败前共运行了多少次。

 #!/usr/bin/env bash

 n=$(( RANDOM % 100 ))

 if [[ n -eq 42 ]]; then
    echo "Something went wrong"
    >&2 echo "The error was using magic numbers"
    exit 1
 fi

 echo "Everything went according to plan"
  1. 本节课我们讲解了 find 命令的 -exec 参数非常强大,它可以对我们查找对文件进行操作。但是,如果我们要对所有文件进行操作呢?例如创建一个zip压缩文件?我们已经知道,命令行可以从参数或标准输入接受输入。在用管道连接命令时,我们将标准输出和标准输入连接起来,但是有些命令,例如tar 则需要从参数接受输入。这里我们可以使用xargs 命令,它可以使用标准输入中的内容作为参数。 例如 ls | xargs rm 会删除当前目录中的所有文件。
    您的任务是编写一个命令,它可以递归地查找文件夹中所有的HTML文件,并将它们压缩成zip文件。注意,即使文件名中包含空格,您的命令也应该能够正确执行(提示:查看 xargs的参数-d

  2. (进阶) 编写一个命令或脚本递归的查找文件夹中最近使用的文件。更通用的做法,你可以按照最近的使用时间列出文件吗?

参考:
https://missing-semester-cn.github.io/2020/shell-tools/
(《The Linux Command Line》[Fifth Internet Edition] William Shotts)

猜你喜欢

转载自blog.csdn.net/m0_45338067/article/details/108257794