这是 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
变量中。其中,$x
为foo
, $y
为 bar
, $z
为 baz
。
另外要留意的一处是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
命令就可以了。