这是 Bash One-Liners Explained 系列的第二篇文章。在这一篇里,我会给你们介绍如何用 Bash 来完成各种各样的字符串操作。我会选择用最合适的 Bash 方法,各种常见的语法和技巧,向各位阐明如何用 Bash 内置的命令和 Bash 编程语言来完成各式各样的任务。

1. 生成从 a 到 z 的字母表

$ echo {a..z}

这一行命令用到了括号展开(Brace expansion)功能,它可以用于生成任意的字符串。{x..y} 是一个序列表达式,其中 x 和 y 都是单个字符,这个表达式展开后包含 x 与 y 之间的所有字符。

运行上面的命令会生成从 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. 生成从 a 到 z 的字母表,字母之间不包含空格

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

这是一个 99.99% 的人都不知道的非常棒的技巧。如果你在printf命令之后指定一个列表,最终它会循环依次打印每个元素,直到完成为止。

在这一行命令中,printf 的格式为"%c",代表一个字符(character),后面的参数是从 a 到 z 的字符列表,字符之间以空格分隔。所以,当printf执行时,它依次输出每个字符直到所有字符全被处理完成为止。

下面是执行的结果:

abcdefghijklmnopqrstuvwxyz

输出的结果最后不包含换行符,因为printf的输出格式是"%c",其中并没有包含\n。如果你想输出完整的一行,可以简单地在字符列表后面增加一个$'\n'

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

$'\n'代表换行符,这是一个常用的技巧。

另外一种方式是,通过 echo 来输出 printf 的结果:

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

这一行命令用到了命令替换功能:执行printf "%c" {a..z}命令然后用执行的输出替换命令。然后,echo 打印输出结果时会带上换行符。

如果你想要每一行仅输出一个字母,在字符后面增加一个换行符:

$ 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. 输出从 00 到 09 的数字

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

这里我们又用到了printf的循环输出功能,这一次的输出格式为"%02d ",意思是在输出数字的时候,如果不满两位就用0补齐。同时,输出的元素是 0 到 9的列表(括号展开后的结果)。

输出结果:

00 01 02 03 04 05 06 07 08 09

如果你使用的是最新的 Bash 4 版本,你可以使用加强的括号展开功能:

$ echo {00..09}

老版本不包含该特性。

4. 生成 30 个英文单词

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

这是一个滥用括号展开的例子,看看最终输出的结果是什么:

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。首先,它取出第一个括号中的第一个元素a,然后依次与第二个括号{1,2,3}的所有元素组合,生成a1 a2 a3,依此类推。

5. 重复输出 10 次字符串

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

这一行命令两次利用了括号展开功能,字符串foo与10个空字符串组合,最终生成10分拷贝:

foo foo foo foo foo foo foo foo foo foo foo

6. 拼接字符串

$ echo "$x$y"

这一行命令简单地将两个变量的值连接在一起,所以如果x变量的值为foo,而y的值为bar,则结果为foobar

注意,这里的"$x$y"是加了双引号的,如果忘记加了,echo会将$x$y当成常规的命令行参数去解析。所以,如果$x在开头包含-,它就变成一个命令行参数,而不是被 echo 输出的内容。

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

执行后的输出:

foo

相反,正确书写的方式执行后的结果如下所示:

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

不过,如果你要将两个字符串相连的结果赋值给变量,是可以将双引号省略的:

var=$x$y

7. 分割字符串

假设变量$str的值为foo-bar-baz,如果你想按-分割成多个段并遍历它,可以使用read命令,并且设置 IFS 的值为-:

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

这里我们使用read x命令从标准输入读取内容,分割后并依次保存到x y z变量中。其中,$xfoo, $ybar, $zbaz

另外要留意的一处是here-string操作符<<<,可以很方便地将字符串传递给命令的标准输入。在这个例子中,$str的内容传给 read 命令的标准输入。

你也可以将分割后的几个字段保存到数组类型的变量中:

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

在这里,-a 选项告诉read命令将分割后的元素保存到数组parts中。随后,你可以通过${parts[0]}, ${parts[1]}${parts[0]}来访问数组的各个元素,或者通过${parts[@]}来访问所有元素。

8. 逐个字符方式处理字符串

$ while IFS= read -rn1 c; do
    # do something with $c
done <<< "$str"

这里我们通过指定-n1参数,让read命令依次读入一个字符,类似地,-n2说明每次读入两个字符。

9. 将字符串中的 foo 替换成 bar

$ echo ${str/foo/bar}

这一行命令用到了参数展开的另外一种形式:${var/find/replace},找到$var变量中的find字符串,并将它替换成bar

要想替换所有出现的字符串"foo",请使用${var//find/replace}的形式:

$ echo ${str//foo/bar}

10. 检查字符串是否匹配模式

$ if [[ $file = *.zip ]]; then
    # do something
fi

这一行命令是说,如果$file的值匹配*.zip,则执行if语句里的命令。这种语法下的模式是最简单的通配符(glob pattern)匹配,通配符包括* ? [...]。其中,*可以匹配一个或者多个字符, ?只能匹配单个字符,[...]能够匹配任意出现在中括号里面的字符或者一类字符集。

下面是另外一个例子,用来判断回答是否匹配 Y 或者 y:er is Y or y:

$ if [[ $answer = [Yy]* ]]; then
    # do something
fi

11. 检查字符串是否匹配某个正则表达式

$ if [[ $str =~ [0-9]+\.[0-9]+ ]]; then
    # do something
fi

这一行命令检查$str是否能够匹配正则表达式[0-9]+\.[0-9]+,即两个数字中间包含一个点号。正则表达式的规范可以通过 man 手册查询: man 3 regex

12.计算字符串的长度

$ echo ${#str}

这里我们又用到了参数展开(也可以叫参数替换)的语法: ${#str},它返回$str变量值的长度。

13. 从字符串中提取子串

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

这一行命令通过子串提取操作,从字符串hello world中取到了子串world。子串提取操作的语法格式为${var:offset:length},它的意思是说从变量var中,提取第offset个位置(下标从0开始计算)开始的总共length个数的字符。在我们这个例子中,忽略了length,默认会返回所有剩余的字符。

下面是另外一个例子,返回$str变量中第7、8位置的两个字符:

$ echo ${str:7:2}

输出结果为or

14. 转换成大写

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

Bash 中的内置命令 declare 可以用于声明一个变量,或者设置变量的属性。在这个例子中,通过指定-u选项,使得变量$var在赋值时,就会自动地将内容转换成大写的格式。现在你 echo 它,可以看到所有内容已经变成大写了:

$ echo $var
FOO BAR

注意,-u选项也是在 Bash 4 新版本中引入的功能,在低版本下是没有的。类似地,你还可以使用 Bash 4 提供的另外一种参数展开语法${str^^},也可以将字符串转换成太写的格式:

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

15. 转换成小写

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

同上面一条类似,-l选项声明变量的小写属性,使得其值转换成小写的格式:

$ echo $var
foo bar

同样,只有 Bash 4 以及以上的版本才支持-l选项。另外一种方式是使用参数展开语法:

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

我补充一句,如果是 Bash 4 以下,还是老老实实地用tr命令就可以了。

转载请注明转自: 团子的小窝 , 本文固定链接: Bash One-Liners Explained 译文(二)

  1. 内涵笑话's avatar
    内涵笑话 发表于 2014-10-15 10:05:41 回复 #1

    多谢多谢,刚好看到这个,感谢站长分享.

  2. bigi's avatar
    bigi 发表于 2014-07-15 12:38:39 回复 #2

    发现个错字 +---> 14. 转换成太写

  3. 林青's avatar
    林青 发表于 2013-10-19 20:37:23 回复 #3

    @kodango
    翻译的确是个费力活,之前翻译几篇论文,搞了N多天。 你无私的努力应该能帮助到很多人,大赞。

    • kodango's avatar
      发表于 2013-10-19 20:39:33 回复

      @林青:那样最好啦,不过评论太少了。我只能从统计网站上才能知道一天的访问量是多少。。

  4. 林青's avatar
    林青 发表于 2013-10-19 20:33:00 回复 #4

    不错,学到几个实用的技巧,不过不用很难记住,下次要用再过来查,团总翻译功底深厚啊。

    • kodango's avatar
      发表于 2013-10-19 20:35:08 回复

      @林青:英语还是不行,很费力,文章几分钟就看下来了,但是要翻译过来,往往要花几个小时。