Bash 单命令行解释(1)--文件操作
这是一篇网络上技术文章的翻译,原文 是系列技术文章的第一篇。讲解 Bash 命令行及技巧,译者觉得对于入门 Linux 命令行有帮助,翻译出来做为参考,学习它需勤动手练习。感兴趣者可随 原文链接 解读其系列文章进行学习。
翻译的系列文章列表:
- Bash 单命令行解释,第一部分:文件操作 (本篇)
2.Bash 单命令行解释,第二部分:字符串操作
Bash 单命令行解释,第一部分:文件操作
我想使用 shell(外壳)程序超级快地操控系统,所以创作了名为 Bash One-Liners Explained 的系列文章。正如我创作的其它系列文章-- Awk One-Liners Explained,Sed One-Liners Explained 和 Perl One-Liners Explained 一样。若这个 bash 系列文章完成后,我将放出其同名的电子书。我的电子书 有 pdf 格式及方便在手机上看的格式(mobi 和 epub)。同时,像 perl1line.txt 一样,也有纯文本 txt 格式的文档。
在这个系列中,我将利用 bash 最佳实践,多变的 bash 方言和 bash 命令技巧,仅展示利用 bash 内建命令和外部程序命令构建命令流完成各种各样的任务需求。
也可参考我其它的高效使用 bash 的系列文章:
- Working Productively in Bash's Emacs Command Line Editing Mode (comes with a cheat sheet)
- Working Productively in Bash's Vi Command Line Editing Mode (comes with a cheat sheet)
- The Definitive Guide to Bash Command Line History (comes with a cheat sheet)
开始学习
第一部分:文件操作
1. 清空文件(清除文件大小为 0)
$ > file
这行命令使用输出重定向操作符 >
。输出重定向造成文件被打开并写入。如果文件不存在就创建它,若存在就清除文件大小为 0。由于重定向没给文件任何内容,结果就清空了文件。
如果想用一些字符串文本替换文件里的内容,可用如下命令:
$ echo "some string" > file
这将 some string
字符串写入文件
2. 追加字符串写入文件
$ echo "foo bar baz" >> file
这行命令使用另一个输出重定向操作符 >>
,意思追加内容到文件。如果文件不存在就创建它并写入。追加 的内容放到文件尾并放到新行(另起一行)。若不想另起一行,可以使用 echo
命令选项 -n
,形如 echo -n
命令:
$ echo -n "foo bar baz" >> file
3. 读取文件首行并赋值到一个变量
$ read -r line < file
本命令使用内建命令 read
和重定向符 <
。
解释: read
命令从标准输入读取一行文字到 line
变量。-r
命令选项意味着输入保留原始(raw)--意思是字符串中的转义字符反斜杠(\) 保持原样。重定向命令 < file
意味着标准输入被 file
代替 -- 从 file
文件中读取数据。
read
命令读入字符串会按照 IFS
环境变量设置,删除读取的一行字符串中行首、行尾的分隔符。IFS 表示 间隔符(Internal Field Separator) 的含义。用于命令行扩展分隔单词和断行,默认 IFS 设置为 空格(space)、制表符(tab)、回车换行(newline \n) 。如果想保留这些,可利用 IFS
即时清除设置命令:
$ IFS= read -r line < file
上面命令仅限当前命令,并不会改变 IFS 的设置。只用于将文件中的一行文本真正原样读出。
另外的从文件中读出第一行的方法:
$ line=$(head -1 file)
上面方法使用命令执行替换 操作符 $(...)
,它执行括号中 ...
的命令返回其输出。本例中,命令 head -1 file
输出文件的第一行,之后赋值给 line
变量。 使用 $(...)
等同于 `...` ,故也可写成:
$ line=`head -1 file`
然而,$(...)
是优选方法,它简洁,易于嵌套。
4. 一行行地读取文件
$ while read -r line; do
# do something with $line
done < file
这是唯一正确的一行行地读取文件的方法。它将 read
命令放到 while
循环中,当 read
命令遇到文件结束,它返回一个非零正数(代码失败),循环结束。
记住 read
命令会删除前导、结尾的分隔符(空格,tab,回车),若想保留分隔符,使用 IFS
即时清除设置命令:
$ while IFS= read -r line; do
# do something with $line
done < file
若不喜欢循环语句尾加 < file
重定向操作,也可使用管道命令:
$ cat file | while IFS= read -r line; do
# do something with $line
done
5. 从文件中随机读取一行并赋值变量
$ read -r random_line < <(shuf file)
这命令对于 bash ,不是一个明晰的从文件随机读取行的方法。因为它需要借助某些外部程序的帮助。在一台运行现代 Linux 的机器上使用来自 GNU 的核心工具 shut
命令。
这条命令使用进程执行替换操作符 <(...)
,这个进程执行替换操作创建匿名管道并连接进程执行的输出部分到某个命名管道的写入部分,然后 bash 执行这个进程,这样就把执行进程的匿名管道替换成了命名管道(用设备文件描述符表示)。
当 bash 解析 <(shuf file)
命令部分时,它打开一个特殊的设备文件 /dev/fd/n
,其中 n
是一个未使用的文件描述符,然后运行 shut file
进程并将它的输出连接到 /dev/fd/n
,这样 <(shut file)
被替换成 < /dev/fd/n
。实际效果如下:
$ read -r random_line < /dev/fd/n
这将从这个被行随机化过的文件中读取第一行。
下面是借助 GNU 的 sort
命令完成同样目标的另一解决方案。GNU 的 sort
命令通过 -R
命令选项随机化输入。
$ read -r random_line < <(sort -R file)
另外的获取文件中随机一行并赋值变量的方法:
$ random_line=$(sort -R file | head -1)
上面命令通过 sort -R
命令随机化文件后,执行 head -1
取出其第一行。
6. 从文件读取一行并分割前3个单词赋值给3个变量
$ while read -r field1 field2 field3 throwaway; do
# do something with $field1, $field2, and $field3
done < file
如果在 read
命令中指定超过一个的变量名,将会把输入字符串分割成符合变量个数的字段(也称域,依据 IFS
设置的分隔符进行分割。默认是空格、tab、回车),之后,把第1个字段分配给第1个变量,把第2个字段分配给第2个变量,以此类推。但是,最后一个字段是字符串剩下的全部。这就是我们最后使用一个不用的变量(throwaway)的原因,若没有它,也许 field3
中并不是我们期望的值。
经常我们会用 _
更短的形式替换 throwaway
做为弃用的变量名:
$ while read -r field1 field2 field3 _; do
# do something with $field1, $field2, and $field3
done < file
当然,若你能保证字符串中只包含3个字段,你完全可以不安排这个无用的变量名:
$ while read -r field1 field2 field3; do
# do something with $field1, $field2, and $field3
done < file
这里有个例子。我们知道,当想知道一个文件内容有多少行、多少单词,多少字符时,在文件上运行 wc
命令,将输出这3个数值和文件名,如下所示:
$ cat file-with-5-lines
x 1
x 2
x 3
x 4
x 5
$ wc file-with-5-lines
5 10 20 file-with-5-lines
因此,这个文件有5行,10个单词和20个字符(空格、回车都算)。我们可使用 read
命令获取这些数值并放到变量中。演示如下:
$ read lines words chars _ < <(wc file-with-5-lines)
$ echo $lines
5
$ echo $words
10
$ echo $chars
20
类似地,可以使用 即入字符串(here-strings) 将字符串分割成多个字段赋值给变量。假设有个字符串 "20 packets in 10 seconds" 保存在 $info
变量中,现将 20 和 10 取出放到2个变量中。不久前这个任务我写过:
$ packets=$(echo $info | awk '{ print $1 }')
$ duration=$(echo $info | awk '{ print $4 }')
然而,若善用 read
命令和 bash 的能力,使用如下一条简单命令可达成目标:
$ read packets _ _ duration _ <<< "$info"
这里的 <<<
是 bash 中的一种重定向机制,被称为 即入字符串(here-strings)重定向 操作符,用于直接将一个字符串重定向到标准输入传递给命令。
7. 获取文件大小并赋值变量
$ size=$(wc -c < file)
上面的一行命令使用命令执行替换操作符 $(...)
(在第3段--3.读取文件首行并赋值到一个变量--中已讲过)。本命令中将 wc -c < file
执行结果,即得出的 file 的大小赋值给变量 size 。(译注:思考并实验这里为什么使用 wc -c < file
而不使用 wc -c file
?)
8. 从全路径字符串中取出文件名
让我们看个例子,假设有个 "/path/to/file.txt" 字符串表示文件的全路径,只想取出其中的文件名 file.txt ,该如何做呢? 一个好的解决方案使用 bash 外壳程序的 参数展开(parameter expansion) 机制。演示如下:
$ pvar = "/path/to/file.txt"
$ filename=${pvar##*/}
如上命令,使用形如 ${varname##pattern}
的参数展开 操作符。这个展开操作试图从$varname
变量表示的字符串开始按 pattern 进行模式匹配,若匹配成功,将从 $varname 字符串删除掉匹配的子字符串后的剩余部分返回。(删除匹配)
上面例子的情况 */
模式将在 "/path/to/file.txt" 中 从头到尾 匹配任意字符后跟 '/' 的模式,由于 */
是贪心匹配,故匹配结果是 /path/to/ 。按照展开逻辑,表达式将返回 删除匹配 的子串,所以,变量 $filename 将等于 file.txt 。(其中 ## 表示 从头到尾 开始模式匹配)
9. 从全路径字符串中取出目录名
与前一任务命令相似,这次让我们从 "/path/to/file.txt" 中取出目录名 /path/to/ 子串。同样使用 参数展开 操作,命令如下:
$ pvar = "/path/to/file.txt"
$ dirname=${pvar%/*}
这次 ${varname%pattern}
的参数展开 操作是 从尾到头 的匹配模式(其中 % 表示 从尾到头 开始模式匹配)。
例子中,,模式为 /* 。从尾到头匹配后,匹配结果为 /file.txt 。将匹配删除后的结果为 /path/to 。
10. 快速创建文件副本
让我们看一下将 /path/to/file 表示的文件,在同目录下拷贝一个副本命令。通常你会写成如下命令:
$ cp /path/to/file /path/to/file_copy
然而,你可以使用 大括号展开 机制,命令写成如下简短形式:
$ cp /path/to/file{,_copy}
大括号展开 机制是将大括号中的每个条目依次展开(枚举每个条目)。例子中 /path/to/file{,_copy}
将会被展开成 /path/to/file /path/to/file_copy
,整个命令将变成 cp /path/to/file /path/to/file_copy
。
相似地,可以快速移动(改名)一个文件,如下命令:
$ mv /path/to/file{,_old}
命令展开成 mv /path/to/file /path/to/file_old
。
欢迎指正
享受文中所说的方法和技巧使用的乐趣吧,并且让我知道您的想法。也许我漏掉了什么,非常乐意收到您的指正。
本作品采用《CC 协议》,转载必须注明作者和本文链接