Shell Tools and Scripting

洪彬
2023-12-01

重定向符号

在 Linux 中,常用的重定向符号有以下几种:

  • >: 将标准输出重定向到一个文件中,如果文件已经存在,则会覆盖原文件。
  • >> : 将标准输出重定向到一个文件中,如果文件已经存在,则会在原文件末尾追加内容。
  • < : 将标准输入重定向到一个文件中。
  • 2> : 将标准错误输出重定向到一个文件或设备中。
  • &> : 将标准输出和标准错误输出重定向到一个文件中。
  • | : 管道符号,将前面命令的输出作为后面命令的输入。
command > output.txt  #将 command 的输出重定向到 output.txt 文件中
command 2> error.txt #将 command 的错误输出重定向到 error.txt 文件中
command < input.txt  #将 input.txt 文件中的内容作为 command 的输入
command1 | command2  #将 command1 的输出作为 command2 的输入

Exercises

1. Read man ls and write an ls command that lists files in the following manner

  • Includes all files, including hidden files
  • Sizes are listed in human readable format (e.g. 454M instead of 454279954)
  • Files are ordered by recency
  • Output is colorized

A sample output would look like this:

 -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 ..

答案:ls -alhtr --colorls -a -l -h -t -r --color
解析:The options used in this command are:(这个命令中使用的选项有:)
-a: includes hidden files in the list(显示隐藏文件)
-l: lists files in long format, which includes information about the file permissions, owner, group, size, and timestamp(以长格式列出文件,包括有关文件权限、所有者、组、大小和时间戳的信息)
-h: displays file sizes in human readable format(以人类可读的格式显示文件大小)
-t: orders files by recency (most recently modified files are listed first)(按时间顺序显示文件,最近修改的文件列在最前面)
-r: reverses the order of the files, so that the most recently modified files are listed last(反转文件的顺序,使最近修改的文件排在最后)
--color: enables colorized output(启用彩色输出)

Note that this command will not work on all systems, as the --color option is not supported by all versions of ls.(请注意,此命令不会在所有系统上工作。因为并非所有版本的ls都支持--color选项。)

2. Write bash functions marco and polo that do the following.

   Whenever you execute marco the current working directory should be saved in some manner, then when you execute polo, no matter what directory you are in, polo should cd you back to the directory where you executed marco. For ease of debugging you can write the code in a file marco.sh and (re)load the definitions to your shell by executing source marco.sh.

答案:

# marco function saves the current working directory in a global variable called MARCO_LOCATION

function marco() {
  MARCO_LOCATION=$(pwd)
  echo "Saved current directory as Marco location: $MARCO_LOCATION"
}

# polo function changes the current directory to the value stored in the MARCO_LOCATION variable
function polo() {
  if [ -z "$MARCO_LOCATION" ]; then
    echo "Error: Marco location not set. Please execute the 'marco' command first."
  else
    cd "$MARCO_LOCATION"
    echo "Changed directory to Marco location: $MARCO_LOCATION"
  fi
}

解析:
  To use these functions, save the code in a file called marco.sh and then run source marco.sh to load the definitions into your current shell session. Then you can execute the marco and polo commands as needed.

  source marco.sh是一个 bash 命令,它的作用是在当前 shell 会话中加载 marco.sh脚本中定义的函数和变量。这意味着,如果你在 marco.sh 中定义了某些函数或变量,那么在执行 source marco.sh 后,你就可以在当前 shell 会话中使用这些函数或变量。这是一种常用的方法,用于将脚本中定义的内容加载到当前 shell 中,以便在 shell 会话中使用。
  例如,假设你在 marco.sh 中定义了一个名为 marco 的函数,那么你可以在执行 source marco.sh 后在 shell 中输入marco并按 Enter 键来执行该函数。

  function polo() 是一个 bash 函数,名为 polo,定义在marco.sh中。函数的功能是将当前工作目录更改为先前通过 marco 函数保存的目录(称为 MARCO_LOCATION)。该函数首先使用 if 语句检查是否已设置 MARCO_LOCATION。如果未设置,则会打印错误消息并提示先执行 marco 命令。否则,函数会使用 cd 命令将当前工作目录更改为 Marco 位置,并打印更改的目录的路径。例如,假设当前工作目录为 /tmpMARCO_LOCATION/home/user/project。如果你在 shell 中输入 polo 并按Enter 键,则函数会将当前工作目录更改为 /home/user/project

额外的补充:
  在 bash 中,所有变量都是字符串类型。所以,变量可以是一个空字符串,也可以是一个非空字符串。

3. Write a bash script to debug

  Say you have a command that fails rarely. In order to debug it you need to capture its output but it can be time consuming to get a failure run. Write a bash script that runs the following script until it fails and captures its standard output and error streams to files and prints everything at the end. Bonus points if you can also report how many runs it took for the script to fail.

 #!/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"

答案:
  第一步,输入vim fail_rarely.sh,把题目中的代码写入到fail_rarely.sh中,并保存。第二步,sudo chmod a+x fail_rarely.sh,修改fail_rarely.sh的权限,使其可以运行。第三步, vim debug.sh,写入以下代码:

#!/usr/bin/env bash

# Initialize counter and output files
i=0
stdout_file=$(mktemp)
stderr_file=$(mktemp)

# Run the script until it fails
while true; do
  i=$((i+1))
  bash fail_rarely.sh &> >(tee -a "$stdout_file") 2> >(tee -a "$stderr_file" >&2)
  if [ $? -ne 0 ]; then
    break
  fi
done

# Print the output
echo "The script failed after $i runs"
echo "Standard output:"
cat "$stdout_file"
echo
echo "Standard error:"
cat "$stderr_file"

# Clean up
rm "$stdout_file" "$stderr_file"

  之后,sudo chmod a+x debug.sh,修改debug.sh的权限,使其可以运行。之后,./debug.sh运行debug.sh即可。

4. write a command that recursively finds all HTML files in the folder and makes a zip with them

  As we covered in the lecture find’s -exec can be very powerful for performing operations over the files we are searching for. However, what if we want to do something with all the files, like creating a zip file? As you have seen so far commands will take input from both arguments and STDIN. When piping commands, we are connecting STDOUT to STDIN, but some commands like tar take inputs from arguments. To bridge this disconnect there’s the xargs command which will execute a command using STDIN as arguments. For example ls | xargs rm will delete the files in the current directory.
  Your task is to write a command that recursively finds all HTML files in the folder and makes a zip with them. Note that your command should work even if the files have spaces (hint: check -d flag for xargs).
  If you’re on macOS, note that the default BSD find is different from the one included in GNU coreutils. You can use -print0 on find and the -0 flag on xargs. As a macOS user, you should be aware that command-line utilities shipped with macOS may differ from the GNU counterparts; you can install the GNU versions if you like by using brew.
  翻译:从讲座中我们可以知道,find 的 -exec 参数可以用来对我们搜索的文件执行操作。但是,如果我们想对所有文件做些事情,比如创建 zip 文件,该怎么办呢?到目前为止,你已经看到命令可以从参数和 STDIN 两个地方获取输入。当管道命令时,我们将 STDOUT 连接到 STDIN,但是像 tar 这样的命令会从参数获取输入。为了解决这个问题,我们可以使用 xargs 命令,它会使用 STDIN 作为参数执行命令。例如,ls | xargs rm 会删除当前目录中的文件。
  你的任务是编写一条命令,递归地查找文件夹中的所有 HTML 文件,并将它们打包成 zip 文件。注意,即使文件名中包含空格,你的命令也应该能正常工作(提示:请检查 xargs 的 -d 标志)。
  如果你使用的是 macOS,请注意,macOS 上默认的 BSD find 命令与 GNU coreutils 中包含的 find 命令不同。你可以在 find 上使用 -print0 参数,在 xargs 上使用 -0 参数。作为 macOS 用户,你应该知道,macOS 附带的命令行工具可能与 GNU 对应版本不同;如果你喜欢,你可以使用 brew 安装 GNU 版本。

答案:
  要递归地查找文件夹中的所有 HTML 文件并创建一个 ZIP 文档,可以使用以下命令:

find . -name "*.html" -print0 | xargs -0 -I {} zip -r html_files.zip "{}"find . -name "*.html" -print0 | xargs -0 zip html_files.zip

  find 命令搜索所有扩展名为 .html 的文件,-print0 选项告诉它以 null 字符分隔结果。xargs 命令则将 find 的输出作为参数传递给 zip 命令,从而创建具有指定名称(在本例中为 html_files.zip)的 ZIP 归档。-0 标志告诉 xargs 期望 null 分隔的输入,因为 find 使用了 -print0 选项。
  -I {}xargs命令的一个选项。它指定每个输入行应替换为命令中的{}。这允许命令将文件名作为单个参数传递给zip命令,从而正确地处理包含空格的文件名。例如,如果有一个文件名为file name.html,则命令将会被解释为:zip html_files.zip "file name.html",而不是:zip html_files.zip file name.html。这样就可以正确地处理文件名中包含空格的情况。
  这个命令应该在大多数类 Unix 系统(包括 Linux 和 macOS)上都能正常工作。

5. (Advanced) Write a command or script to recursively find the most recently modified file in a directory. More generally, can you list all files by recency?

答案:find . -type f -printf '%T@ %p\n' | sort -n | tail -1 | cut -f2- -d" "
  该命令使用find命令递归查找所有文件,然后使用printf选项输出文件的修改时间(用时间戳表示)和文件名。 然后使用sort命令对输出的行按时间戳进行排序,并使用tail命令输出最后一行。 最后,使用cut命令删除时间戳,只保留文件名。
  要将所有文件按修改时间排序,可以在上述命令中删除tail -1,这样就会输出所有文件的文件名。
  -printf '%T@ %p\n'find命令的一个选项。它指定find命令输出文件的修改时间(用时间戳表示)和文件名。具体来说,%T@是一个占位符,它会被替换为文件的修改时间(以时间戳格式表示)。%p是另一个占位符,它会被替换为文件的名称。最后,\n是换行符,用于在输出中换行。因此,-printf '%T@ %p\n'会输出每个文件的修改时间和文件名,以时间戳和文件名之间的空格分隔。例如,对于一个名为file.txt的文件,输出可能看起来像这样:1609361438 file.txt
  sort -n是一个命令,它使用数字排序对输入的行进行排序。具体来说,sort命令接受输入的行,并按照字典序对行进行排序。 -n选项指定使用数字排序,而不是字典序排序。这意味着如果输入的行包含数字,则它们将按照数字大小而不是字典序排序。
  tail -1是一个命令,它输出输入的最后一行。具体来说,tail命令用于输出文件的最后几行。 -1选项指定输出最后一行。如果要输出最后两行,可以使用tail -2
  cut -f2- -d" "是一个命令,它从输入的每一行中删除第一个字段,并输出剩余的字段。具体来说,cut命令用于从文本文件中提取一部分信息。 -f选项指定要输出的字段,而-d选项指定用于分隔字段的分隔符。在这种情况下,-f2-指定输出从第二个字段开始的所有字段。 -d" "指定空格为分隔符,因此字段是由空格分隔的。

  script可以这样写:

#!/usr/bin/env bash

files=$(find . -type f)

for file in $files
do
    modified_time=$(stat -c %Y "$file")
    printf "%s %s\n" "$modified_time" "$file"
done | sort -n | tail -1 | cut -f2- -d" "

一些其他小问题

如何清除一个已经设置的变量?

  要清除(或者说“卸载”)一个已经设置的变量,可以使用 unset 命令。
  例如,假设你想要清除变量 VARNAME,可以使用以下命令:
  unset VARNAME
  这条命令会删除变量 VARNAME 及其值。如果你再次使用变量 VARNAME,就会得到一个未设置的变量错误消息。
  请注意,使用 unset 命令只会清除变量本身,而不会影响其他与变量相关的内容。

如何列出所有已经设置的变量?

  要列出所有已经设置的变量,可以使用 set 命令。set 命令有很多选项,但是如果你不使用任何选项,它会列出所有已经设置的变量。例如,可以使用以下命令:
set
  这个命令会列出所有已经设置的变量。
  还可以使用 env 命令来列出所有环境变量。例如,可以使用以下命令:
env
  这个命令会列出所有环境变量,包括由系统设置的变量和用户设置的变量。
  注意,set 命令只会列出本地变量,而 env 命令会列出所有环境变量。本地变量是在当前 shell 中设置的变量,而环境变量是系统级别的变量,并且可以在所有进程中使用。

 类似资料:

相关阅读

相关文章

相关问答