Shell 工具和脚本:学习笔记

写在前面:本篇内容来自于 MIT 推出的课程:计算机教育中缺失的一课,这门课程介绍了命令行、强大的文本编辑器的使用、使用版本控制系统提供的多种特性等等。中文课程主页:https://missing-semester-cn.github.io/

本篇为第二节课,主题是 Shell 工具和脚本,在这节课中,介绍了 bash 作为脚本语言的一些基础操作,以及几种最常用的shell工具。

  • 变量赋值: foo=bar,注意中间不能添加空格

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

  • 函数编写:使用 "$1"获取变量值

    mcd () {
        mkdir -p "$1"
        cd "$1"
    }
    
    
  • bash中的特殊变量

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

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

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

  • 程序示例,变量为文件名,如果文件名中包含“foobar”,不进行操作,否则加入”foobar“

    #!/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
            # 这里的空格不能少
            echo "File $file does not have any foobar, adding one"
            echo "# foobar" >> "$file"
        fi
    done
    
    
  • 批量处理文件时非常有用:bash 通配(globbing)。

    • 通配符 - 当你想要利用通配符进行匹配时,可以分别使用 ?* 来匹配一个或任意个字符
    • 花括号 {} - 当你有一系列的指令,其中包含一段公共子串时,可以用花括号来自动展开这些命令。这在批量移动或转换文件时非常方便。
    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
    
    
  • 检查shell脚本中的错误工具:shellcheck,有程序,也有插件

  • 查找帮助: man 程序,但是输出太长,可以使用 tldr 程序,输出几个例子(非常有用!)

  • 查找文件: findfdfd 默认支持使用正则查找,更加符合直觉。 locate 使用数据库的方式更加快速的搜索,但是缺陷是只能通过文件名来查找。

  • 查找代码: grep 有很多选项,这也使它成为一个非常全能的工具。其中我经常使用的有 -C :获取查找结果的上下文(Context)-v 将对结果进行反选(Invert),也就是输出不匹配的结果。举例来说, grep -C 5 会输出匹配结果前后五行。当需要搜索大量文件的时候,使用 -R 会递归地进入子目录并搜索所有的文本文件。但是,我们有很多办法可以对 grep -R 进行改进,例如使其忽略.git 文件夹,使用多CPU等等。因此出现了很多替代品:比较常用的是 ripgrep (rg) ,因为它速度快,而且用法非常符合直觉。例子如下:

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

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

    • ranger
    • autojump,命令 j

习题部分

  1. ls 命令

    1. 显示所有文件: ls -a
    2. 以可以理解的格式输出: ls -lh
    3. 以最近访问顺序排序: ls -tl
  2. 编写两个bash函数: marco 和 polo 执行下面的操作。 每当你执行 marco 时,当前的工作目录应当以某种形式保存,当执行 polo 时,无论现在处在什么目录下,都应当 cd 回到当时执行 marco 的目录。 为了方便debug,你可以把代码写在单独的文件 marco.sh 中,并通过 source marco.sh命令,(重新)加载函数。

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

    #!/usr/bin/env bash
    
    count=0
    while true; do
        n=$((RANDOM % 100))
    
        if [[ n -eq 42 ]]; then
            echo "Something went wrong"
            echo >&2 "The error was using magic numbers"
            echo "Program successfully ran $count times\\n"
            exit 1
        fi
        ((count += 1))
        echo "ran successfully"
    done
    
    echo $count
    echo "Everything went according to plan"
    
    

    执行: sh [capture.sh](<http://capture.sh/>) 1> output 2> error; cat output; cat error

  4. 您的任务是编写一个命令,它可以递归地查找文件夹中所有的HTML文件,并将它们压缩成zip文件。注意,即使文件名中包含空格,您的命令也应该能够正确执行(提示:查看 xargs的参数-d,译注:MacOS 上的 xargs没有-d,查看这个issue)

    如果您使用的是 MacOS,请注意默认的 BSD find 与GNU coreutils 中的是不一样的。你可以为find添加-print0选项,并为 xargs 添加 -0 选项。作为 Mac 用户,您需要注意 mac 系统自带的命令行工具和 GNU 中对应的工具是有区别的;如果你想使用 GNU 版本的工具,也可以使用 brew 来安装。

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

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值