Bash 单命令行解释(2)--字符串操作

这是翻译系列文章的第2篇,参考原文链接
翻译的系列文章列表:

  1. Bash 单命令行解释,第一部分:文件操作
  2. Bash 单命令行解释,第二部分:字符串操作(本篇)

Bash 单命令行解释,第二部分:字符串操作

这是 Bash One-Liners Explained 系列文章的第2篇。本文中我将利用 bash 最佳实践,多变的 bash 方言和 bash 命令技巧,展示仅利用 bash 内建命令和外部程序命令构建命令流完成各种各样的任务需求。

参考系列文章的 第一篇 Bash 单命令行解释,第一部分:文件操作 。若这个 bash 系列文章完成后,我将放出其同名的电子书。我的电子书 有 pdf 格式及方便在手机上看的格式(mobi 和 epub)。同时,像 perl1line.txt 一样,也有纯文本 txt 格式的文档。

也可参考我其它的高效使用 bash 的系列文章:

开始学习

第二部分:字符串操作

1. 创建输出字母表

$ echo {a..z}

以上命令使用 bash 的 大括号展开(brace expansion) 机制,其含义是生成「字面字符串」序列(枚举每个条目)。此例中表达式是 {x..z} ,其中 x 和 z 是单个字符,这个表示字母表中从 x 到 z 的所有字符(包含 x 和 z)(注意:x 和 z 之间是2个 . 符号)。

若运行以上命令,其输出 a-z 的所有字母:

$ echo {a..z}
a b c d e f g h i j k l m n o p q r s t u v w x y z

2. 无空格分隔输出字母表中字母

$ printf "%c" {a..z}

这个神奇的 bash 技巧 99.99% 的使用者不知道。如果把一个列表传递个 printf 命令, printf 命令会循环依次处理列表的每个条目,循环结束,命令退出。这是一个极佳的技巧。

以上命令使用 "%c" 作为 printf 命令的格式输出控制符。(与 C 语言中函数类似)它将列表序列中的字母依次输出。

下面是命令运行输出结果:

abcdefghijklmnopqrstuvwxyz

这个输出后没有回车,因为列表序列中不包含回车符( '\n' )。加入行终止,只需在字符序列尾加入回车符,方法是在列表序列表达式尾加上 $'\n' ,命令如下:

$ printf "%c" {a..z} $'\n'

$'\n' 是 bash 方便表示,代表回车符。命令输出 {a..z} 后,接着输出回车符。(注意:printf 类似 C 语言中的 printf 函数,接受可变参数)。

另一方法使用 echo 命令输出 printf "%c" {a..z} 命令结果。这里使用了命令执行替换机制(第一篇文讲解过)。命令如下:

$ echo $(printf "%c" {a..z})

如上命令,使用命令执行替换(command substitution)输出字母表传递给 echo 命令输出, echo 命令默认在命令后输出回车。

想以列方式输出字母?在 printf 命令的格式输出控制中加入回车符!

$ printf "%c\n" {a..z}
a
b
...
z

想将 printf 命令展开的列表赋值给变量?使用 -v 命令选项:

$ printf -v alphabet "%c" {a..z}

这将 "abcdefghijklmnopqrstuvwxyz" 字符串赋值给 $alphabet 变量。

类似地,可创建整数列表序列,我们试验一下从 1 到 100 :

$ echo {1..100}

输出:

1 2 3 ... 100

也可使用 seq 命令工具创建数字序列,替换上述的方法:

$ seq 1 100

3. 给 0 到 9 数字序列中每个数加前导 0

$ printf "%02d " {0..9}

这里我们再次使用 printf 命令的循环能力。 这次 printf 的格式控制字符串是 ""%02d" ,它的意思是输出的数字以0前导补足数字到2位。后面的命令参数通过大括号展开传递进来从 0 到 9 的数字。
输出:

00 01 02 03 04 05 06 07 08 09

如果使用的 bash 版本大于 4,也可使用下面的大括号展开表达式:

$ echo {00..09}

老的 bash 版本没有这个特性。

4. 生成 30 个英语单词

$ echo {w,t,}h{e{n{,ce{,forth}},re{,in,fore,with{,al}}},ither,at}

上面的命令是对大括号展开(brace expansion) 机制滥用(也能很好理解它)。来看看它的输出:

when whence whenceforth where wherein wherefore wherewith wherewithal whither what then thence thenceforth there therein therefore therewith therewithal thither that hen hence henceforth here herein herefore herewith herewithal hither hat

太神奇了!

它怎样做到的! 在 大括号展开 机制中,在大括号中的每个被逗号 , 分隔的条目都可以分别交叉放置。例如,如果像下面的命令:

$ echo {a,b,c}{1,2,3}

上面的大括号表达式将会产生 a1 a2 a3 b1 b2 b3 c1 c2 c3 ,其首先使用第1个大括号中的 a 依次和第2个大括号中的 1,2,3 组合,构成 a1 a2 a3 ,接着使用第1个大括号中的 b 依次和第2个大括号中的 1,2,3 组合,构成 b1 b2 b3 ,接着是 c 。(矩阵乘的概念)

所以,上面生成单词的命令就会构成上面那些单词的输出!

5. 生成1个字符串的10个副本

$ echo foo{,,,,,,,,,,}

上述命令再次使用大括号展开机制。前面的字符串 foo 和后面的10个条目都为空的列表的每个条目组合,形成字符串 foo 的10个副本:

foo foo foo foo foo foo foo foo foo foo foo

6. 合并连接2个字符串

$ echo "$x$y"

这条命令简单合并连接2个变量,如果 x 变量包含字符串 foo ,且 y 变量包含字符串 bar ,结果为 foobar

注意命令中 "$x$y" 是被双引号括住的。若不括住它,echo 命令会优先将 $x$y 变量中的值解释为命令行参数或命令选项。因此,若 $x 变量的值与命令行参数或命令选项相同(以 - 开始的字符等),将被按命令参数或命令选项解释,使结果不能按我们的意愿输出:

x=-n
y=" bar"
echo $x$y

输出:

foo

相反,合并连接2个字符串方法:

x=-n
y=" foo"
echo "$x$y"

输出:

\-n foo

若想合并连接2个字符串赋值到另一个变量,就不必要使用双引号括住:

var=$x$y

7. 以指定字符分割字符串

若有个字符串变量 $str ,其值为 foo-bar-baz ,你想把它按横杠符 - 分割成3个字符串。可以使用 IFSread 命令的组合:

$ str=foo-bar-baz
$ IFS=- read -r x y z <<< "$str"

这里,使用 read x y z 命令读取进入的数据并将它分割成3个变量,使用 IFS=- 命令行即时设置分隔符为 -read 命令选项 -r 表示保持原样。

这行命令结果将使 $x 被赋值 foo$y 被赋值 bar$z 被赋值 baz

另外注意 <<<即入字符串(here-string) 的操作符,其使一个字符串重定向为标准输入。这里,$str 变量的字符串做为了 read 命令的输入。

也可把分割开的变量放到一个数组变量中:

$ IFS=- read -ra parts <<< "foo-bar-baz"

命令 read-a 命令选项意味着将分割的字符串赋值给数组变量 parts ,之后,可通过 ${parts[0]}${parts[1]} 分别访问数组中的元素,也可使用 ${parts[@]} 访问所有元素。(译注:若直接使用 $parts 将访问 ${parts[0]} 即变量中的第1个元素。自己实践验证)

8. 依次处理字符串中的字符

$ while IFS= read -rn1 c; do
    # 对变量 $c 即字符串中的字符操作
done <<< "$str"

这里使用 read 命令选项 -n1 一次从输入字符串中读1个字符处理。类似地可以使用 n2 命令选项指示一次从输入字符串中读2个字符进行处理等:

9. 在字符串中用 "bar" 替换 "foo"

$ str=baz-foo-bar-foo-foo
$ echo ${str/foo/bar}
baz-bar-bar-foo-foo

命令 echo ${str/foo/bar} 使用 bash 的参数展开机制,它在 $str 中找到 foo 最开始出现位置并将其用 bar 替换。很简单吧!

bar 替换所有出现的 foo ,如下命令表达式:

$ echo ${str//foo/bar}
baz-bar-bar-bar-bar

10. 检查一个字符串模式匹配

$ if [[ $file = *.zip ]]; then
    # 执行一些操作
fi

这里如果 $file 变量的字符串值模式匹配 *.zip (包含 .zip 子字符串)。这是个简单的模式匹配。通配符 * 表示匹配任意数量的任意字符(包括空白符),通配符 ? 匹配任意单个字符,[...] 表示匹配 [] 中的任一字符。(参考 bash 手册中的文件名展开的模式匹配)

下面是另一个例子,判断屏幕回答是 Yy 开始的字符串:

# read answer
$ if [[ $answer = [Yy]* ]]; then
    # 执行一些操作
fi

11. 判断一个字符串匹配正则表达式

$ if [[ $str =~ [0-9]+.[0-9]+ ]]; then
    # 执行一些操作
fi

以上命令以扩展正则表达式 [0-9]+.[0-9]+ 判断变量 $str 是否匹配。正则表达式 [0-9]+.[0-9]+ 含义,匹配 数字+1个任意字符+数字 的组合。详见手册 man 3 regex 命令。(译注:命令中双目运算符 =~ 等同 == ,只表示使用扩展正则表达式,前面例子中的 = 等同 == 是为与 POSIX 规范兼容)

12. 获取字符串长度

$ echo ${#str}

这里利用参数展开机制,${#str} 表示返回变量 $str 值的长度,非常简单!(译注:前面第7节的例子中,有个数组变量 $parts ,若执行命令 $ echo ${#parts} 将返回数组元素个数,输出 3 )

13. 从字符串中按位置取出子串

$ str="hello world"
$ echo ${str:6}

以上命令从字符串 "hello world" 中取出子串 "world" 。它使用子字符串展开机制。更一般的形式 ${var:offset:length} ,注意:1. 字符串位置以 0 开始记,故例子中 offset 值 6 指向字符 w 。2. 若省略 length 将从位置 取到字符串尾。

下面是另一个例子:

$ echo ${str:7:2}

输出:

or

14. 大写化字符串

$ declare -u var
$ var="foo bar"

bash 的declare 命令声明一个变量的同时也可赋予变量一些特性。本例中,通过命令选项 -u 使声明的变量 var 的值大写化。意味着之后无论给 var 变量赋值什么字符串,都将自动全部变成大写:

$ echo $var
FOO BAR

注意命令选项 -u 在 bash 版本 4 之后引入。类似地可以使用 bash 4 的另一参数展开特性完成大写转换的功能,形如: ${var^^} 表达式:

$ str="zoo raw"
$ echo ${str^^}

输出:

ZOO RAW

15. 小写化字符

$ declare -l var
$ var="FOO BAR"

类似于 -u 命令选项, 选项 -l 对于 declare 命令是使变量 var 小写化:

$ echo $var
foo bar

选项 -l 也同样是在 bash 版本 4 之后有效。

另一方法使用形如 ${var,,} 的参数展开表达式使 var 变量值转换小写:

$ str="ZOO RAW"
$ echo ${str,,}

输出:

zoo raw

欢迎指正

享受文中所说的方法和技巧使用的乐趣吧,并且让我知道您的想法。也许我漏掉了什么,非常乐意收到您的指正。

本作品采用《CC 协议》,转载必须注明作者和本文链接
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!