The Missing Semester of Your CS Education -----Shell

在程序间创建连接

在shell(命令行界面)中,程序有两个主要的“流”:输入流和输出流。程序在尝试读取信息时,它们会从输入流中进行读取,当程序打印信息时,它们会将信息输出到输出流中。通常,一个程序的输入输出流都是你的终端。也就是说,你的键盘作为输入,显示器作为输出。但是,我们也可以重定向这些流!

重定向输入输出流

最简单的重定向是 < file> file。这两个命令可以将程序的输入输出流分别重定向到文件:

  • echo hello > hello.txt:将字符串 “hello” 输出到文件 hello.txt 中。
  • cat hello.txt:读取并显示文件 hello.txt 的内容。
  • cat < hello.txt:与上面的命令等价,从文件 hello.txt 中读取内容并显示。
  • cat < hello.txt > hello2.txt:从文件 hello.txt 中读取内容并写入到文件 hello2.txt 中。
  • cat hello2.txt:读取并显示文件 hello2.txt 的内容。

通过这些操作,我们可以看到输入和输出流的重定向如何将内容从一个地方转移到另一个地方。

追加内容和管道

  • >>:这个符号用于向文件追加内容而不是覆盖。例如:

    echo world >> hello.txt
    

    这会在 hello.txt 文件的末尾追加 “world”。

  • 管道(pipes):使用管道 | 符号,我们可以将一个程序的输出连接到另一个程序的输入。这使我们能够更好地利用程序的输出进行进一步处理。例如:

    ls -l | tail -n1
    

    这条命令会列出当前目录的详细文件列表(ls -l),并将结果传给 tail -n1,从中显示最后一行。

  • 更多示例:

    curl --head --silent google.com | grep --ignore-case 219
    

    这条命令使用 curl 获取 google.com 的头信息,并使用 grep 查找包含“219”的行。

这些命令展示了如何通过重定向和管道将不同程序的输入输出流连接起来,以实现更复杂的操作。

总结

  • >:重定向输出到文件,覆盖原有内容。
  • <:重定向输入从文件读取。
  • >>:重定向输出到文件,追加内容。
  • |:将一个程序的输出作为另一个程序的输入。

这些重定向和管道操作在shell编程中非常常用,能够大大提高命令行操作的灵活性和效率。

解释 ls -l | tail -n1

这个命令实际上是由两个部分组成的:

  1. ls -l
  2. tail -n1

这两个命令通过管道 | 连接起来。

1. ls -l

ls 是一个列出目录内容的命令,-l 是一个选项,表示以长格式列出目录内容。长格式会显示文件的详细信息,包括权限、链接数、所有者、文件大小、修改时间和文件名。例如:

-rw-r--r--  1 user  group   1234 Jun  6 12:34 file1.txt
-rw-r--r--  1 user  group   5678 Jun  6 12:34 file2.txt
2. tail -n1

tail 是一个显示文件末尾内容的命令,-n1 表示显示最后一行。

管道 |

管道 | 将一个命令的输出作为下一个命令的输入。在这个例子中,ls -l 的输出会被传递给 tail -n1,然后 tail -n1 显示最后一行。

综合起来

ls -l | tail -n1 的作用是:

  1. ls -l 列出当前目录中所有文件的详细信息。
  2. 管道 |ls -l 的输出传递给 tail -n1
  3. tail -n1 只显示传递过来的数据中的最后一行。

示例

假设你的当前目录包含以下文件:

$ ls -l
total 8
-rw-r--r--  1 user  group   1234 Jun  6 12:34 file1.txt
-rw-r--r--  1 user  group   5678 Jun  6 12:35 file2.txt

执行 ls -l | tail -n1 的结果会是:

-rw-r--r--  1 user  group   5678 Jun  6 12:35 file2.txt

因为 tail -n1 只显示 ls -l 输出的最后一行,也就是 file2.txt 的信息。

总结

ls -l | tail -n1 这个命令组合用于列出当前目录中最后一个文件(按 ls -l 列表顺序)的详细信息。通过这种方式,你可以快速查看目录中最新添加或最后一个文件的详细信息。

指令

curl --head --silent google.com | grep --ignore-case content-length | cut --delimiter=' ' -f2

这条指令实际上是由四个部分组成的,每个部分通过管道 | 连接起来:

  1. curl --head --silent google.com
  2. grep --ignore-case content-length
  3. cut --delimiter=' ' -f2

下面详细解释每个部分的作用及其组合:

1. curl --head --silent google.com

  • curl 是一个命令行工具,用于从服务器传输数据。
  • --head 选项表示仅获取HTTP头部信息,不获取正文内容。
  • --silent 选项表示静默模式,不显示进度条或错误信息。
  • google.com 是目标网址。

这一部分命令的作用是获取 google.com 网站的HTTP头部信息。HTTP头部信息包含关于网页和服务器的元数据,例如服务器类型、内容类型、内容长度等。

2. grep --ignore-case content-length

  • grep 是一个命令行工具,用于搜索文本中的模式。
  • --ignore-case 选项表示忽略大小写进行搜索。
  • content-length 是要搜索的模式。

这一部分命令的作用是从 curl 获取的HTTP头部信息中搜索包含 content-length 的行。Content-Length 头部字段表示响应体的大小(以字节为单位)。

3. cut --delimiter=' ' -f2

  • cut 是一个命令行工具,用于从文本中剪切指定的部分。
  • --delimiter=' ' 选项表示以空格作为字段分隔符。
  • -f2 选项表示提取第二个字段。

这一部分命令的作用是从包含 Content-Length 的行中提取第二个字段,也就是内容长度的值。

综合起来

整个命令的作用是:

  1. 使用 curl 获取 google.com 网站的HTTP头部信息。
  2. 使用 grep 从头部信息中搜索包含 Content-Length 的行。
  3. 使用 cut 从搜索到的行中提取内容长度的值。

示例输出

假设执行以下命令:

curl --head --silent google.com | grep --ignore-case content-length | cut --delimiter=' ' -f2

你可能会得到类似的输出:

219

这表示 google.com 的HTTP响应内容长度为 219 字节。

具体指令分解

  1. 获取HTTP头部信息

    curl --head --silent google.com
    

    可能输出:

    HTTP/2 200 
    content-type: text/html; charset=ISO-8859-1
    content-length: 219
    ...
    
  2. 搜索 Content-Length 字段

    curl --head --silent google.com | grep --ignore-case content-length
    

    可能输出:

    content-length: 219
    
  3. 提取 Content-Length 的值

    curl --head --silent google.com | grep --ignore-case content-length | cut --delimiter=' ' -f2
    

    输出:

    219
    

通过这种方式,你可以使用一条命令直接获取目标网页的内容长度。

修改系统

在Linux系统中,通过命令行修改系统文件时,尤其是需要超级用户权限的文件,可能会遇到权限问题。具体来看你的情况:

分析你的命令和错误

  1. 查找亮度文件

    sudo find -L /sys/class/backlight -maxdepth 2 -name '*brightness*'
    
    • sudo:使用超级用户权限运行命令。
    • find -L:在 /sys/class/backlight 目录下查找文件,-L 选项表示跟随符号链接。
    • -maxdepth 2:限制查找深度为2。
    • -name '*brightness*':匹配名称中包含 brightness 的文件。

    结果找到了文件 /sys/class/backlight/thinkpad_screen/brightness

  2. 进入目录

    cd /sys/class/backlight/thinkpad_screen
    

    进入到包含 brightness 文件的目录。

  3. 修改亮度文件

    sudo echo 3 > brightness
    

    尝试使用超级用户权限将 3 写入 brightness 文件时,出现错误:

    An error occurred while redirecting file 'brightness'
    open: Permission denied
    

问题的原因

这个问题的根本原因是shell重定向 > 操作是在普通用户权限下执行的,而不是在 sudo 提升的权限下执行的。即使你使用了 sudo echo 3,重定向部分 > brightness 依然在普通用户权限下运行,因此会导致权限被拒绝。

解决方法

为了正确地以超级用户权限重定向输出,可以使用以下两种方法:

方法1:使用 tee 命令

tee 命令可以读取标准输入并将其内容写入文件,同时也输出到标准输出。使用 sudo tee 可以解决权限问题:

echo 3 | sudo tee brightness

这里,echo 33 写入标准输入,通过管道 | 传递给 sudo tee brightness,并将其内容写入 brightness 文件。

方法2:使用 sudo sh -c 命令

另一种方法是通过 sudo sh -c 执行整个命令,这样重定向部分也在超级用户权限下执行:

sudo sh -c 'echo 3 > brightness'

这条命令使用 sudo 提升权限运行 sh,然后在 sh 中执行 echo 3 > brightness,这样整个命令在超级用户权限下运行,避免了权限被拒绝的问题。

总结

重定向操作 > 的权限问题可以通过 teesudo sh -c 方式解决。具体执行方式:

# 方法1:使用 tee 命令
echo 3 | sudo tee /sys/class/backlight/thinkpad_screen/brightness

# 方法2:使用 sudo sh -c 命令
sudo sh -c 'echo 3 > /sys/class/backlight/thinkpad_screen/brightness'

选择你更喜欢的一种方法即可。这样就能成功将亮度值写入 brightness 文件。

当然,我会详细解释图片中的内容。

Bash 脚本的控制结构和参数

控制结构

与其他大多数编程语言一样,bash 也支持 ifcasewhilefor 这些控制流关键字。同时,bash 也支持函数。它可以接受参数并基于参数进行操作。

创建目录并进入

以下是一个例子函数,它会创建一个文件夹并使用 cd 进入该文件夹:

mcd() {
    mkdir -p "$1"
    cd "$1"
}
  • mcd():定义一个名为 mcd 的函数。
  • mkdir -p "$1":创建目录,-p 选项表示递归创建目录(如果需要的话),"$1" 是传递给函数的第一个参数。
  • cd "$1":进入由第一个参数指定的目录。

特殊变量

bash 中,与其他脚本语言不同的是,bash 使用了很多特殊的变量来表示参数、错误代码和相关变量。以下是一些常用的特殊变量:

  • $0:脚本名。
  • $1$9:脚本的参数。$1 是第一个参数,依此类推。
  • ${10}:第十个及后面的参数需要用大括号括起来。
  • $@:所有参数,作为单独的字符串。
  • $#:参数个数。
  • $?:前一个命令的返回值(退出状态)。
  • $$:当前脚本的进程ID。
  • !!:完整的上一个命令,包括参数。常见应用:当你因为权限不足执行命令失败时,可以使用 sudo !! 再尝试一次。
  • $_:上一个命令的最后一个参数。如果你正在使用的是交互式 shell,你可以通过按下 Esc 之后按下 . 键来获取这个值。

示例解释

函数 mcd

假设我们定义并调用这个函数:

mcd mydir

执行步骤:

  1. mcd 函数被调用,$1 的值为 mydir
  2. mkdir -p "mydir" 创建名为 mydir 的目录(如果不存在则创建)。
  3. cd "mydir" 进入 mydir 目录。
特殊变量示例
  • $0:假设脚本名为 myscript.sh,则 $0 的值是 myscript.sh
  • $1:如果运行命令 ./myscript.sh arg1 arg2,则 $1 的值是 arg1
  • $@:对于上面的命令,$@ 的值是 arg1 arg2
  • $#:对于上面的命令,$# 的值是 2(表示有两个参数)。
  • $?:如果上一个命令成功执行,$? 的值是 0。如果失败,值为非零。
  • $$:假设当前脚本的进程ID为 12345,则 $$ 的值是 12345
  • !!:如果上一个命令是 echo hello,则 !! 的值是 echo hello
  • $_:如果上一个命令是 echo hello,则 $_ 的值是 hello

总结

  • bash 支持丰富的控制流和函数定义。
  • 可以通过特殊变量方便地处理脚本的参数、错误状态和进程信息。
  • 利用 mkdircd 结合,可以快速创建并进入目录。
  • 特殊变量在脚本编写中非常有用,可以帮助处理参数和命令的执行状态。

当然,我会详细解释图片中的内容。

标准输出和标准错误

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

退出码的使用

退出码可以搭配 &&(与操作符)和 ||(或操作符)使用,用来进行条件判断,决定是否执行其他程序。它们都属于短路运算符(short-circuiting),即在一行的多个命令可以用 ; 分隔。

  • true 程序 的返回码永远是 0,表示成功。
  • false 程序 的返回码永远是 1,表示失败。

让我们来看几个例子:

示例解释

false || echo "Oops, fail"
# Oops, fail
  • false 返回 1(失败),所以 echo "Oops, fail" 会被执行并输出 “Oops, fail”。
true || echo "Will not be printed"
# 
  • true 返回 0(成功),由于短路特性,echo "Will not be printed" 不会被执行。
true && echo "Things went well"
# Things went well
  • true 返回 0(成功),所以 echo "Things went well" 会被执行并输出 “Things went well”。
false && echo "Will not be printed"
# 
  • false 返回 1(失败),由于短路特性,echo "Will not be printed" 不会被执行。
true ; echo "This will always run"
# This will always run
  • true 返回 0(成功),然后 echo "This will always run" 被执行并输出 “This will always run”。
  • 注意 ; 号并不会根据前一个命令的返回值决定是否执行后一个命令,而是无论前一个命令是否成功,都会执行下一个命令。

总结

  • 短路运算符
    • &&(与操作符):只有前一个命令成功(返回码为 0)时才执行后一个命令。
    • ||(或操作符):只有前一个命令失败(返回码非 0)时才执行后一个命令。
  • 分号 ;
    • 分隔多个命令,前一个命令执行结束后,不论成功与否,都会执行后一个命令。

这些控制结构在编写Bash脚本时非常有用,可以用来处理条件执行和错误处理,确保脚本的健壮性和可靠性。

命令替换 (Command Substitution)

命令替换是一种常用的模式,可以通过将命令的输出赋值给变量或直接在命令中使用。命令替换有两种形式:

  1. $(CMD)
  2. `CMD`(注意这里使用的是反引号)

使用 $(CMD) 的形式执行命令时,它的输出结果会替换掉 $(CMD)。例如,如果执行 for file in $(ls),shell 首先将 ls 的输出结果获取,然后遍历这些返回值。

进程替换 (Process Substitution)

进程替换是命令替换的一种特殊形式,用于将命令的输出作为一个临时文件。形式是:

  1. <(CMD)

例如:

diff <(ls foo) <(ls bar)

会显示文件夹 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
    # 如果"foobar"不存在于文件中,grep返回退出状态为1
    # 我们将标准输出和标准错误流都重定向到/dev/null,因为我们并不关心这些信息
    if [[ $? -ne 0 ]]; then
        echo "File $file does not have any foobar, adding one"
        echo "# foobar" >> "$file"
    fi
done

代码解释

echo "Starting program at $(date)"
  • $(date):命令替换,执行 date 命令并将其输出插入到字符串中。
  • 效果是输出当前的日期和时间。
echo "Running program $0 with $# arguments with pid $$"
  • $0:脚本名。
  • $#:传递给脚本的参数个数。
  • $$:当前脚本的进程ID。
  • 效果是输出脚本名、参数个数和脚本的进程ID。
for file in "$@"
  • "$@":传递给脚本的所有参数,每个参数作为独立的字符串。
  • 遍历传递给脚本的每个文件。
grep foobar "$file" > /dev/null 2> /dev/null
  • grep foobar "$file":搜索文件中是否包含字符串 foobar
  • > /dev/null 2> /dev/null:将标准输出和标准错误流都重定向到 /dev/null,即丢弃这些输出。
if [[ $? -ne 0 ]]; then
  • $?:前一个命令的退出状态码。
  • -ne 0:判断退出状态码是否不等于0,表示 grep 没有找到 foobar 字符串。
  • 如果找不到字符串 foobar,则执行以下语句:
echo "File $file does not have any foobar, adding one"
echo "# foobar" >> "$file"
  • 输出提示信息,并将 # foobar 追加到文件末尾。

其他解释

test 命令和 [[ ]] 比较

  • 在Bash中,可以使用 test 命令(或简写 [)进行比较操作。
  • [[ ]] 是Bash的扩展语法,比 [ 更加安全和功能强大,适用于更复杂的条件判断。

例如:

if [[ -f "$file" ]]; then
    echo "File exists"
fi

总结

  • 命令替换:用 $(CMD)`CMD` 执行命令并插入其输出。
  • 进程替换:用 <(CMD) 将命令输出作为临时文件使用。
  • 特殊变量:如 $0$#$$$? 用于获取脚本名、参数个数、进程ID 和上一个命令的退出状态码。
  • 比较和条件判断:用 [[ ]] 进行安全的条件判断。

这些特性使得Bash脚本在处理系统任务时非常强大和灵活。

grep foobar "$file" > /dev/null 2> /dev/null 这条命令涉及到标准输出和标准错误流的重定向。让我们详细分解这条命令:

详细解释

  • grep foobar "$file":在文件 $file 中搜索字符串 foobar
  • > /dev/null:将标准输出(stdout)重定向到 /dev/null
  • 2> /dev/null:将标准错误(stderr)重定向到 /dev/null

什么是标准输出和标准错误

  • 标准输出(stdout):程序正常运行时输出的信息。默认情况下,这些信息会显示在终端上。
  • 标准错误(stderr):程序运行过程中产生的错误信息。默认情况下,这些错误信息也会显示在终端上。

/dev/null

  • /dev/null 是一个特殊的设备文件,它会丢弃写入其中的所有数据。常用来丢弃不需要的输出。

重定向符号

  • >:重定向标准输出。比如,command > file 会将 command 的标准输出写入到 file 文件中。
  • 2>:重定向标准错误。比如,command 2> file 会将 command 的标准错误写入到 file 文件中。
  • &>:同时重定向标准输出和标准错误到同一个文件。比如,command &> file 会将 command 的标准输出和标准错误都写入到 file 文件中。

解释 grep foobar "$file" > /dev/null 2> /dev/null

  • > /dev/null:将 grep 命令的标准输出重定向到 /dev/null,即丢弃所有正常输出。
  • 2> /dev/null:将 grep 命令的标准错误重定向到 /dev/null,即丢弃所有错误输出。

这意味着无论 grep 是否找到字符串 foobar,它的输出和错误信息都会被丢弃,不会在终端上显示。唯一能反映 grep 结果的是它的退出状态码:

  • 如果 grep 找到 foobar,退出状态码为 0
  • 如果 grep 没找到 foobar,退出状态码为 1
  • 如果发生其他错误,退出状态码为 2

脚本上下文

在脚本中使用这种重定向方式可以避免不必要的输出干扰脚本的执行,只需根据退出状态码 $? 来判断 grep 的结果。例如:

#!/bin/bash

for file in "$@"; do
    grep foobar "$file" > /dev/null 2> /dev/null
    if [[ $? -ne 0 ]]; then
        echo "File $file does not have any foobar, adding one"
        echo "# foobar" >> "$file"
    fi
done

这段脚本会检查每个传递给脚本的文件是否包含 foobar,如果不包含,就在文件末尾添加 # foobar

总结

  • > /dev/null:重定向标准输出到 /dev/null,丢弃正常输出。
  • 2> /dev/null:重定向标准错误到 /dev/null,丢弃错误输出。

这种方式常用于需要执行命令但不希望显示任何输出的情况。

当然,我会详细解释图片中的内容。

通配符 (Globbing)

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

  • 通配符:当你想利用通配符进行匹配时,你可以分别使用 ?* 来匹配一个或任意多个字符。例如:

    • 对于文件 foofoo1foo2foo10barrm foo? 这条命令会删除 foo1foo2,而 rm foo* 则会删除 foo 之内的所有文件。
  • 花括号 {}:当你有一系列的指令,其中包含公共子字符串时,可以用花括号来自动展开这些命令。这在批量移动或转换文件时非常方便。

示例

通配符使用示例:
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 文件到 folder 目录
创建多个目录:
mkdir foo bar
创建多个文件:
touch {foo,bar}/{a..h}
# 会创建foo/a, foo/b, ..., foo/h, bar/a, bar/b, ..., bar/h这些文件

比较两个文件夹中的差异:

diff <(ls foo) <(ls bar)

当然,diff <(ls foo) <(ls bar) 是一个常见的用来比较两个目录内容差异的命令。让我们详细解释一下它的工作原理和作用。

进程替换 (Process Substitution)

在这个命令中,使用了进程替换 (Process Substitution)。进程替换允许将命令的输出作为文件来使用。形式如下:

  • <(COMMAND):执行 COMMAND 并将其输出作为一个临时文件。
  • >(COMMAND):创建一个临时文件,并将其内容作为 COMMAND 的输入。

diff 命令

diff 命令用于比较文件或目录的差异。它逐行比较文件的内容,并显示不同之处。

ls 命令

ls 命令用于列出目录中的文件和子目录。

综合解释

diff <(ls foo) <(ls bar) 的作用是比较 foo 目录和 bar 目录的文件列表,并显示它们之间的差异。

具体步骤
  1. ls foo

    • 列出 foo 目录中的文件和子目录。
  2. ls bar

    • 列出 bar 目录中的文件和子目录。
  3. <(ls foo)<(ls bar)

    • 通过进程替换,将 ls fools bar 的输出分别作为两个临时文件。
  4. diff

    • 比较这两个临时文件,显示 foobar 目录中的文件列表差异。

示例

假设目录结构如下:

foo/
  a.txt
  b.txt
bar/
  a.txt
  c.txt

执行 diff <(ls foo) <(ls bar) 命令后,输出可能是:

1c1
< b.txt
---
> c.txt
输出解释
  • 1c1:表示第一行不同。
  • < b.txt:表示 foo 目录中有 b.txt 文件。
  • ---:分隔符。
  • > c.txt:表示 bar 目录中有 c.txt 文件。

完整解释

  • < 符号:表示左边(foo 目录)的内容。
  • > 符号:表示右边(bar 目录)的内容。

这表明 foo 目录中有 b.txt 文件,而 bar 目录中有 c.txt 文件,这两个文件在对方目录中不存在。

总结

diff <(ls foo) <(ls bar) 命令利用进程替换,将两个 ls 命令的输出作为临时文件来比较。这使得我们可以方便地比较两个目录中的文件列表差异,从而找出哪些文件在一个目录中存在而在另一个目录中不存在。

Shell 脚本和函数的区别

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

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

#!/usr/bin/env python

import sys
for arg in reversed(sys.argv[1:]):
    print(arg)
  • shebang#!/usr/bin/env python 这一行告诉操作系统使用 Python 解释器来执行这个脚本,而不是 shell。

函数和脚本的区别

  • 语言:函数能与 shell 使用相同的语言,脚本可以使用任意语言。因此在脚本中包含 shebang 是很重要的。
  • 加载时间:函数仅在定义时加载,脚本会在每次被执行时加载。这让函数的加载比脚本略快一些,但每次修改函数定义时,都要重新加载一次。
  • 环境:函数在当前的 shell 环境中执行,脚本会在单独的进程中执行。因此,函数可以直接改变当前工作目录,脚本则不行。脚本需要使用 export 将环境变量导出,并传递给子进程。
  • 模块化:与其他编程语言一样,函数可以提高代码模块化,代码复用性并创建清晰的结构。shell 脚本中往往也会包含它们自己的函数定义。

示例脚本

#!/bin/bash

echo "Starting program at $(date)" # 输出当前日期和时间
echo "Running program $0 with $# arguments with pid $$" # 输出脚本名、参数个数和进程ID

for file in "$@"; do
    grep foobar "$file" > /dev/null 2> /dev/null
    if [[ $? -ne 0 ]]; then
        echo "File $file does not have any foobar, adding one"
        echo "# foobar" >> "$file"
    fi
done

总结

  • 通配符:使用 *? 进行匹配,使用 {} 进行批量命令展开。
  • 函数与脚本:了解它们的差异,shebang 的重要性。
  • 工具:使用 shellcheck 等工具来帮助检查和调试脚本错误。
  • shebang:提高脚本的可移植性和可执行性。

这些特性使得 bash 脚本在处理文件和目录操作时非常强大和灵活。

当然,我会详细解释图片中的内容。

Shell 工具

查看命令如何使用

在使用命令行时,你可能会遇到一些疑问,比如如何为特定的命令找到合适的标记(选项)?例如 ls -lmv -imkdir -p。为了更好地理解命令的使用方法和选项,我们有几种常见的方法。

  1. 搜索网络

    • 你可以在网上搜索相关问题,找到许多社区资源(如StackOverflow)来帮助你。
  2. 系统自带帮助

    • 大多数UNIX系统都自带了帮助文档,可以直接查看命令的使用说明。

常见的获取帮助的方法

  1. -h--help 标志

    • 这是最常用的方式,许多命令都支持这些标记来获取简单的帮助信息。例如:
      ls --help
      
  2. man 命令

    • manmanual(手册)的缩写,它提供了命令的用户手册。
    • 例如,运行 man rm 会输出 rm 命令的详细说明,同时还会有其标记(选项)列表,包括 -i 选项。
    • man 手册的内容通常非常详细,适合查阅命令的所有可能选项和用法。

示例

man rm

这条命令会显示 rm 命令的手册,包括它的用途、所有选项以及如何使用。

TLDR pages

  • TLDR pages 是一个很好的替代品,它提供了简洁的说明和常见用法的例子,非常适合快速查阅。
  • 例如,搜索 tldr tartldr ffmpeg 可以快速找到这两个命令的常见用法。

使用 TLDR 示例

tldr tar
tldr ffmpeg

这两条命令会显示 tarffmpeg 命令的简洁用法示例,非常适合快速参考。

总结
  • 使用 -h--help:快速查看命令的帮助信息。
  • 使用 man 命令:查看详细的用户手册。
  • 使用 TLDR pages:获取简洁的使用示例。

这些方法可以帮助你更好地理解和使用命令行工具,提高工作效率。

当然,我会详细解释图片中的内容。

查找文件

在程序开发和维护中,一个常见的重要任务是查找文件或目录。UNIX系统包含一个名为 find 的工具,它是shell上用于查找文件的绝佳工具。find 命令会通过过滤条件来搜索符合条件的文件。例如:

示例

# 查找所有名为 src 的文件夹
find . -name src -type d

# 查找所有路径中包含 test 的 python 文件
find . -path '*/test/*.py' -type f

# 查找前一天修改过的所有文件
find . -mtime -1

# 查找所有大小在500k至10M的tar.gz文件
find . -size +500k -size -10M -name '*.tar.gz'

对找到的文件进行操作

find 命令不仅可以列出所有找到的文件,还能对这些文件进行操作,从而简化某些批处理任务。

示例

# 删除当前目录及其子目录中所有以 .tmp 结尾的文件
find . -name '*.tmp' -exec rm {} \;

# 查找所有的 PNG 文件并将其转换为 JPG 格式
find . -name '*.png' -exec convert {} {}.jpg \;

在这里:

  • -exec 选项用于对找到的每个文件执行指定的命令。
  • {}find 用于匹配文件路径的占位符。
  • \; 用于表示 -exec 命令的结束。

使用简化工具

尽管 find 用途广泛,它的语法却比较难记。为了解决这个问题,用户可以使用别名(alias)或寻找更友好的替代工具。一个很好的替代工具是 fd

fd 工具

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

fd PATTERN

提高搜索效率

许多人已经知道如何使用 findfd,但可能不知道如何进一步提高搜索效率。例如,如果你要每次都重新搜索文件,可以考虑编译索引或建立数据库的方式来实现更快速地搜索。

locate 工具

locate 使用一个由 updatedb 负责更新的数据库,在大多数系统中会通过 cron 每日更新。它能比 find 更快地查找文件,因为它查找的是已建立的文件数据库,而不是实时遍历文件系统。例如:

locate filename

总结

  • find 命令:强大且灵活的文件查找工具,支持复杂条件和批处理操作。
  • fd 工具:简化版的 find,使用更直观,性能更高。
  • locate 工具:基于数据库的文件查找工具,速度快。

查找代码

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

为了实现这一点,很多类 UNIX 的系统都提供了 grep 命令,它是用于对输入文本进行匹配的通用工具。grep 是一个非常重要的 shell 工具

grep 命令

grep 有许多选项,使它成为一个非常全能的工具。常用的选项包括:

  • -c:获取查找结果的上下文(Context)。
  • -v:将结果进行反选(Invert),即输出不匹配的结果。
  • -R:递归搜索目录及其子目录。

举例来说,grep -C 5 会输出匹配结果前后五行。

示例
# 查找文件中包含字符串 'import requests' 的行
grep 'import requests' filename.py

# 查找文件中包含字符串 'import requests' 的行,并递归搜索当前目录及其子目录中的所有文件
grep -R 'import requests' .

grep -R 进行改进

有很多方法可以对 grep -R 进行改进,例如使其忽略 .git 文件夹,使用多 CPU 等等。

替代工具

一些更为高级的替代工具包括 ackagrg。它们功能都很好用,但 rg (ripgrep) 是我比较常用的,因为它速度快,而且用法非常符合直觉。

rg (ripgrep) 示例
# 查找所有包含 'import requests' 的文件
rg 'import requests'

# 查找所有含有 'shebang' 的文件(包含隐藏文件)
rg -t py '#!' 

# 查找所有含有 'foo' 字符串的文件,并打印文件名
rg -l 'foo'

# 显示匹配的统计信息(匹配的行数和文件数量)
rg --stats PATTERN

总结

find/fd 一样,重要的是你要知道在特定问题下使用合适的工具,这样会事半功倍。具体选择哪个工具并不那么重要,重要的是你要知道这些工具的功能和用法,并能够根据需要选择最合适的工具。

rg (ripgrep) 常用参数解释

1. 查找所有包含 ‘import requests’ 的文件
rg 'import requests'
  • 这个命令会递归搜索当前目录及其子目录中的所有文件,并输出包含字符串 'import requests' 的行。
2. 查找所有含有 ‘shebang’ 的文件(包含隐藏文件)
rg -t py '#!'
  • -t py:指定文件类型为 Python 文件(.py)。
    • -t 参数用于指定文件类型,这样可以限制搜索范围。例如,你可以使用 -t py 来只搜索 Python 文件,或者使用 -t js 来只搜索 JavaScript 文件。
    • 这个参数有助于提高搜索效率,尤其是在大型项目中,只需要搜索特定类型的文件。
  • #'!':表示 shebang,通常在脚本文件的开头,用于指定解释器。rg 将会在所有 Python 文件中搜索包含 #! 的行。
  • -u:包含隐藏文件和文件夹(未在命令中显示,但可以作为有用的信息)。
3. 查找所有含有 ‘foo’ 字符串的文件,并打印文件名
rg -l 'foo'
  • -l:只输出包含匹配字符串的文件名,而不是匹配的行。
    • 这个参数非常有用,当你只需要知道哪些文件包含某个字符串,而不需要知道具体的行内容时。
4. 显示匹配的统计信息(匹配的行数和文件数量)
rg --stats PATTERN
  • --stats:显示搜索的统计信息,包括匹配的行数、文件数量、搜索的总时间等。
    • 这个参数非常有用,当你需要了解搜索的详细信息和性能数据时。

其他常用参数

  • -i:忽略大小写进行搜索。

    • 例如,rg -i 'foo' 将忽略大小写,搜索 fooFooFOO 等。
  • -w:匹配整个单词。

    • 例如,rg -w 'foo' 只匹配完整的单词 foo,而不会匹配 foobar 中的 foo
  • -C NUM:显示匹配行上下各 NUM 行的上下文。

    • 例如,rg -C 3 'foo' 将显示匹配行以及匹配行前后各3行的内容。
  • -v:反转匹配,显示不包含匹配模式的行。

    • 例如,rg -v 'foo' 将显示所有不包含 foo 的行。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值