本文主要对Shell中的字符串进行简单总结,另外本文所使用的Linux环境为CentOS Linux release 8.1.1911
,所使用的Shell为bash 4.4.19(1)-release
。
一、引用机制
字符串是Shell编程中最常用的数据类型。字符串可以用单引号或双引号引起来,也可以不用引号,还可以使用$
加一对单引号引起来,这几种方式也可以自由组合。示例如下:
引用(Quoting)用来去除Shell中某些字符或单词的特殊含义。它可以用来禁止对特殊字符的特殊处理,防止保留字被识别为特殊字符,并防止参数扩展。每个Shell元字符对Shell都有特殊的含义,必须引起来才能代表它自己。如果使用了命令历史扩展的功能,则历史扩展字符(通常是!
)也引用起来以取消历史扩展。
注意:这里所说的引用(quote)是指字符串周围的引号,而不是C++或Java中对对象地址的引用(reference)。
有三种引用机制:转义字符、单引号和双引号。
上面出现的一些术语解释:
- 元字符(metacharacter):当不加引号时能分隔单词的字符。包括空格、制表符、换行符或以下字符之一:
|
、&
、;
、(
,)
、<
或>
。- 单词(word):被Shell视为一个单元的字符序列,它不能包含未加引号的元字符。
- 保留字(reserved word):对Shell有特殊意义的单词,它们大部分是用来构建Shell的控制结构的,如
for
和while
。- 参数(parameter):参数是存储值的实体。它可以是一个名字,一个数字,或者
#
、*
、@
等特殊字符之一。- 参数扩展(parameter expansion):参数前面加
$
,要扩展的参数(变量)名和符号可以放到大括号{}
中,大括号可选,但可用于保护要扩展的变量不受紧跟在它后面的字符的影响,否则这些字符会被解释成名称的一部分。
1.无引号
不使用引用机制时,就不能去除Shell中某些字符或单词的特殊含义,所有的特殊字符都会进行特殊处理。不使用引用机制并带有部分特殊字符的示例如下:
如上图所示,这些特殊字符会进行特殊处理。直接使用echo
输出时可以有空格:
2.转义字符
没有转义的反斜杠\
是转义字符,它能保留其下一个字符的字面值,除非这个字符是换行符。如果出现\newline
(\
是某行最后一个字符),并且反斜杠本身没有被引用,\newline
将被视为行连续符,也就是说,它将从输入流中删除并有效地忽略。
上面不使用引用机制并带有部分特殊字符的示例中的特殊字符都会被处理,使用转义字符进行转义可以保留它们的字面值,这些特殊字符也就不会进行特殊处理,保持原样输出,一部分特殊字符前面加上转义字符的示例如下:
#!/bin/bash
str1=hello\ world
str2=hello\|world
str3=hello\&world
str4=hello\;world
str5=hello\(world
str6=hello\)world
str7=hello\<world
str8=hello\>world
str9=hello\$world
str10=hello\\world
str11=hello\!world
str12=hello\`pwd\`
str13=hello\'world
str14=hello\"world
echo "str1:${str1}"
echo "str2:${str2}"
echo "str3:${str3}"
echo "str4:${str4}"
echo "str5:${str5}"
echo "str6:${str6}"
echo "str7:${str7}"
echo "str8:${str8}"
echo "str9:${str9}"
echo "str10:${str10}"
echo "str11:${str11}"
echo "str12:${str12}"
echo "str13:${str13}"
echo "str14:${str14}"
执行结果:
转义字符\
后面是换行符并且反斜杠本身没有被引用,一个示例如下:
如上图所示,换行符被视为行连续符,\
和换行符都被忽略。
3.单引号
将字符括在单引号'
中可以保留引号内每个字符的字面值。单引号之间不能再出现单引号,即使它前面有反斜杠。
一些使用单引号的示例如下::
#!/bin/bash
str1='hello world'
str2='hello|world'
str3='hello&world'
str4='hello;world'
str5='hello(world'
str6='hello)world'
str7='hello<world'
str8='hello>world'
str9='hello$world'
str10='hello\world'
str11='hello!world'
str12='hello`pwd`'
str13='hello"world'
str14='hello\$world'
str15='hello\\world'
str16='hello\`pwd\`'
str17='hello\"world'
str18='hello\!world'
str19='hello\&world'
str20='hello"${str1}"world'
echo "str1:${str1}"
echo "str2:${str2}"
echo "str3:${str3}"
echo "str4:${str4}"
echo "str5:${str5}"
echo "str6:${str6}"
echo "str7:${str7}"
echo "str8:${str8}"
echo "str9:${str9}"
echo "str10:${str10}"
echo "str11:${str11}"
echo "str12:${str12}"
echo "str13:${str13}"
echo "str14:${str14}"
echo "str15:${str15}"
echo "str16:${str16}"
echo "str17:${str17}"
echo "str18:${str18}"
echo "str19:${str19}"
echo "str20:${str20}"
执行结果:
单引号之间不能出现单引号,使用转义也不行,示例如下:
最终输出的字符串中确实没有单引号。直接换行和反斜杠后跟换行符,示例如下:
4.双引号
将字符括在双引号"
中可以保留引号内所有字符的字面值,除了$
、`
、\
,以及在启用历史扩展时的!
:
- 字符
$
和`
在双引号内保留其特殊含义。 - 反斜杠仅在后跟
$
、`
、"
、\
或换行符之一时才具有特殊意义,在双引号中,反斜杠后跟这些字符之一,那么该反斜杠将被删除,反斜杠后面的字符没有特殊含义,那么反斜杠和该字符都会被保留,双引号中可以通过反斜杠跟一个双引号的形式出现另一个双引号。 - 如果打开了历史扩展,
!
将导致历史扩展被执行,除非!
用反斜杠进行了转义,并且!
前面的反斜杠并不会被移除;当Shell处于POSIX模式时,!
在双引号内没有特殊含义,即使启用了历史扩展。
一些使用双引号的示例如下:
#!/bin/bash
str1="hello world"
str2="hello|world"
str3="hello&world"
str4="hello;world"
str5="hello(world"
str6="hello)world"
str7="hello<world"
str8="hello>world"
str9="hello$world"
str10="hello\world"
str11="hello!world"
str12="hello`pwd`"
str13="hello'world"
str14="hello\$world"
str15="hello\\world"
str16="hello\`pwd\`"
str17="hello\"world"
str18="hello\'world"
str19="hello\!world"
str20="hello\&world"
str21="hello'${str1}'world"
echo "str1:${str1}"
echo "str2:${str2}"
echo "str3:${str3}"
echo "str4:${str4}"
echo "str5:${str5}"
echo "str6:${str6}"
echo "str7:${str7}"
echo "str8:${str8}"
echo "str9:${str9}"
echo "str10:${str10}"
echo "str11:${str11}"
echo "str12:${str12}"
echo "str13:${str13}"
echo "str14:${str14}"
echo "str15:${str15}"
echo "str16:${str16}"
echo "str17:${str17}"
echo "str18:${str18}"
echo "str19:${str19}"
echo "str20:${str20}"
echo "str21:${str21}"
执行结果:
直接换行和反斜杠后跟换行符,示例如下:
单引号中反斜杠跟换行符最终仍然保持原样,而双引号中反斜杠跟换行符,该反斜杠被删除并且没有换行。命令行中使用!
会进行历史扩展,双引号中有开启历史扩展的!
的示例如下:
!-n
是选择前n条命令。!
前加了反斜杠转义后就不会执行历史扩展了,并且这里的反斜杠也没有被删除。
放在脚本中执行却不会进行历史扩展,脚本如下:
#!/bin/bash
pwd
echo "!"
echo "!-2"
echo "\!-3"
执行结果:
5.ANSI-C引用
形式为$'string '
的单词会被特殊处理。该词将会扩展为字符串,并按照ANSI C标准的规定替换反斜杠转义字符,扩展结果是单引号引用的,就好像$
符号不存在一样。如果其中出现反斜杠转义序列,会按如下转义序列的含义替换:
转义字符 | 含义 |
---|---|
\a | 响铃 |
\b | 退格删除 |
\e、\E | 转义字符(不属于ANSI C) |
\f | 换页 |
\n | 新行 |
\r | 回车 |
\t | 水平制表符 |
\v | 垂直制表符 |
\\ | 反斜杠 |
\’ | 单引号 |
\" | 双引号 |
\? | 问号 |
\nnn | 八位字符,其值为八进制值nnn(一到三个八进制数字) |
\xHH | 八位字符,其值为十六进制值HH(一个或两个十六进制数字) |
\uHHHH | Unicode(ISO/IEC 10646)字符,其值为十六进制值HHHH(一到四个十六进制数字) |
\UHHHHHHHH | Unicode(ISO/IEC 10646)字符,其值为十六进制值HHHHHHHH(一到八个十六进制数字) |
\cx | ctrl-x字符 |
一些使用ANSI C引用扩展字符串的示例如下:
#!/bin/bash
str1=$'hello$world'
str2=$'hello\world'
str3=$'hello!world'
str4=$'hello`pwd`'
str5=$'hello"world'
str6=$'hello\$world'
str7=$'hello\\world'
str8=$'hello\`pwd\`'
str9=$'hello\"world'
str10=$'hello\'world'
str11=$'hello\!world'
str12=$'hello\&world'
str13=$'hello"${str1}"world'
str14=$'hello\bworld'
str15=$'hello\eworld'
str16=$'hello\nworld'
str17=$'hello\rworld'
str18=$'hello\tworld'
str19=$'hello\vworld'
str20=$'hello?world'
str21=$'hello\?world'
str22=$'hello\101world'
str23=$'hello\x2Aworld'
str24=$'hello\u0023world'
str25=$'hello\U00005FC3world'
echo "str1:${str1}"
echo "str2:${str2}"
echo "str3:${str3}"
echo "str4:${str4}"
echo "str5:${str5}"
echo "str6:${str6}"
echo "str7:${str7}"
echo "str8:${str8}"
echo "str9:${str9}"
echo "str10:${str10}"
echo "str11:${str11}"
echo "str12:${str12}"
echo "str13:${str13}"
echo "str14:${str14}"
echo "str15:${str15}"
echo "str16:${str16}"
echo "str17:${str17}"
echo "str18:${str18}"
echo "str19:${str19}"
echo "str20:${str20}"
echo "str21:${str21}"
echo "str22:${str22}"
echo "str23:${str23}"
echo "str24:${str24}"
echo "str25:${str25}"
执行结果:
6.Locale专用翻译
这个暂时用不到
在用双引号引起来的字符串前面加上美元符号$
会导致该字符串根据当前语言环境进行翻译。如果当前语言环境为C或POSIX,或者没有可用的翻译,则忽略美元符号。如果字符串被翻译并替换了,替换的字符串将被双引号引起来。
二、字符串常见操作
1.获取字符串长度
方法:
# string为字符串变量名
${#string}
示例:
2.字符串的拼接
Shell拼接字符串不需要任何运算符,只需要把两个字符串并排放在一起就行。示例:
#!/bin/bash
str1="hello"
str2="world"
result1=${str1}${str2}
result2="${str1} ${str2}"
result3=${str1}','${str2}
result4=${str1}","${str2}
result5="${str1},${str2}"
result6="${str1}java,"hello"${str2}!"
result7="${str1}java"' hello'"${str2}"
echo ${result1}
echo ${result2}
echo ${result3}
echo ${result4}
echo ${result5}
echo ${result6}
echo ${result7}
执行结果:
3.字符串的截取
方法:
# 从string字符串的左边第start个字符开始,向右截取到最后,start从0开始
${string:start}
# 从string字符串的左边第start个字符开始,向右截取length个字符
${string:start:length}
# 从string字符串的右边第start个字符开始,向右截取到最后,start从1开始
${string:0-start}
# 从string字符串的右边第start个字符开始,向右截取length个字符
${string:0-start:length}
# 从string字符串左边第一次出现*chars的位置开始,截取*chars右边的所有字符
${string#*chars}
# 从string字符串左边最后一次出现*chars的位置开始,截取*chars右边的所有字符
${string##*chars}
# 从string字符串右边第一次出现chars*的位置开始,截取chars*左边的所有字符
${string%chars*}
# 从string字符串右边最后一次出现chars*的位置开始,截取chars*左边的所有字符
${string%%*chars*}
示例:
#!/bin/bash
url="https://www.gnu.org/software/bash/manual/bash.html"
# 从左边第8个字符开始向右截取到最后
echo ${url:8}
# 从左边第29个字符开始向右截取4个字符
echo ${url:29:4}
# 从右边第42个字符开始向右截取到最后
echo ${url:0-42}
# 从右边第9个字符开始向右截取4个字符
echo ${url:0-9:4}
# 从左边第一次出现ba的位置开始截取右边的所有字符
echo ${url#*ba}
# 从左边最后一次出现sh的位置开始截取右边的所有字符
echo ${url##*sh}
# 从右边第一次出现ba的位置开始截取左边的所有字符
echo ${url%ba*}
# 从右边最后一次出现sh的位置开始截取左边的所有字符
echo ${url%%sh*}
执行结果: