Bash 支持的字符串操作數量達到了一個驚人的數目。但可惜的是,這些操作工具缺乏一個統一的核心。他們中的一些是參數代換的子集,另外一些則是 UNIX 下 expr
函數的子集。這將會導致語法前后不一致或者功能上出現重疊,更不用說那些可能導致的混亂了
字符串長度
$
expr length $string
上面兩個表達式等價于C語言中的 strlen() 函數
expr "$string" : '.*'
stringZ=abcABC123ABCabc
echo ${#stringZ} # 15
echo `expr length $stringz` # 15
echo `expr "$stringZ" : '.*'` # 15
樣例-1. 在文本的段落之間插入空行
#!/bin/bash
# paragraph-space.sh
# 版本 2.1,發布日期 2012年7月29日
# 在無空行的文本文件的段落之間插入空行。
# 像這樣使用: $0 <FILENAME
MINLEN=60 # 可以試試修改這個值。它用來做判斷。
# 假設一行的字符數小于 $MINLEN,并且以句點結束段落。
#+ 結尾部分有練習!
while read line # 當文件有許多行的時候
do
echo "$line" # 輸出行本身。
len=${#line}
if [[ "$len" -lt "$MINLEN" && "$line" =~ [*{\.}]$ ]]
# if [[ "$len" -lt "$MINLEN" && "$line" =~ \[*\.\] ]]
# 新版Bash將不能正常運行前一個版本的腳本。Ouch!
then echo # 在該行以句點結束時,
fi #+ 增加一行空行。
done
exit
# 練習:
# -----
# 1) 該腳本通常會在文件的最后插入一個空行。
#+ 嘗試解決這個問題。
# 2) 在第17行僅僅考慮到了以句點作為句子終止的情況。
#+ 修改以滿足其他的終止符,例如 ?, ! 和 "。
起始部分字符串匹配長度
expr match "$string" '$substring'
其中,$substring
是一個正則表達式
expr "$string" : '$substring'
其中,$substring
是一個正則表達式
stringZ=abcABC123ABCabc
# |------|
# 12345678
echo `expr match "$stringZ" 'abc[A-Z]*.2'` # 8
echo `expr "$stringZ" : 'abc[A-Z]*.2'` # 8
索引
expr index $string $substring
返回在 $string
中第一個出現的 $substring
字符所在的位置
stringZ=abcABC123ABCabc
# 123456 ...
echo `expr index "$stringZ" C12` # 6
# C 的位置。
echo `expr index "$stringZ" 1c` # 3
# 'c' (第三號位) 較 '1' 出現的更早。
幾乎等價于C語言中的 strchr()
截取字符串(字符串分片)
${string:position}
在 $string
中截取自 $position
起的字符串。
如果參數 $string
是 *
或者 @
,那么將會截取自 $position
起的 位置參數
${string:position:length}
在 $string
中截取自 $position
起,長度為 $length
的字符串
stringZ=abcABC123ABCabc
# 0123456789.....
# 索引位置從0開始。
echo ${stringZ:0} # abcABC123ABCabc
echo ${stringZ:1} # bcABC123ABCabc
echo ${stringZ:7} # 23ABCabc
echo ${stringz:7:3} # 23A
# 三個字符的子字符串。
# 從右至左進行截取可行么?
echo ${stringZ:-4} # abcABC123ABCabc
# ${parameter:-default} 將會得到整個字符串。
# 但是……
echo ${stringZ:(-4)} # Cabc
echo ${stringZ: -4} # Cabc
# 現在可以了。
# 括號或者增加空格都可以"轉義"位置參數。
其中,參數 position
與 length
可以傳入一個變量而不一定需要傳入常量
樣例-2. 產生一個8個字符的隨機字符串
#!/bin/bash
# rand-string.sh
# 產生一個8個字符的隨機字符串。
if [ -n "$1" ] # 如果在命令行中已經傳入了參數,
then #+ 那么就以它作為起始字符串。
str0="$1"
else # 否則,就將腳本的進程標識符PID作為起始字符串。
str0="$$"
fi
POS=2 # 從字符串的第二位開始。
LEN=8 # 截取八個字符。
str1=$( echo "$str0" | md5sum | md5sum )
# ^^^^^^ ^^^^^^
# 將字符串通過管道計算兩次 md5 來進行兩次混淆。
randstring="${str1:$POS:$LEN}"
# ^^^^ ^^^^
# 允許傳入參數
echo "$randstring"
exit $?
# bozo$ ./rand-string.sh my-password
# 1bdd88c4
# 不過不建議將其作為一種能夠抵抗黑客的生成密碼的方法。
如果參數 $string
是 *
或者 @
,那么將會截取自 $position
起,最大個數為 $length
的位置參數
echo ${*:2} # 輸出第二個及之后的所有位置參數。
echo ${@:2} # 同上。
echo ${*:2:3} # 從第二個位置參數起,輸出三個位置參數。
expr substr $string $position $length
在 $string
中截取自 $position
起,長度為 $length
的字符串
stringZ=abcABC123ABCabc
# 123456789......
# 索引位置從1開始。
echo `expr substr $stringZ 1 2` # ab
echo `expr substr $stringZ 4 3` # ABC
expr match "$string" '\($substring\)'
在 $string
中截取自 $position
起的字符串,其中 $substring
是正則表達式
expr "$string" : '\($substring\)'
在 $string
中截取自 $position
起的字符串,其中 $substring
是正則表達式。
stringZ=abcABC123ABCabc
# =======
echo `expr match "$stringZ" '\(.[b-c]*[A-Z]..[0-9]\)'` # abcABC1
echo `expr "$stringZ" : '\(.[b-c]*[A-Z]..[0-9]\)'` # abcABC1
echo `expr "$stringZ" : '\(.......\)'` # abcABC1
# 上面所有的形式都給出了相同的結果。
expr match "$string" '.*\($substring\)'
從 $string
結尾部分截取 $substring
字符串,其中 $substring
是正則表達式
expr "$string" : '.*\($substring\)'
從 $string
結尾部分截取 $substring
字符串,其中 $substring
是正則表達式
stringZ=abcABC123ABCabc
# ======
echo `expr match "$stringZ" '.*\([A-C][A-C][A-C][a-c]*\)'` # ABCabc
echo `expr "$stringZ" : '.*\(......\)'` # ABCabc
刪除子串
${string#substring}
刪除從 $string
起始部分起,匹配到的最短的 $substring
${string##substring}
刪除從 $string
起始部分起,匹配到的最長的 $substring
stringZ=abcABC123ABCabc
# |----| 最長
# |----------| 最短
echo ${stringZ#a*C} # 123ABCabc
# 刪除 'a' 與 'c' 之間最短的匹配。
echo ${stringZ##a*C} # abc
# 刪除 'a' 與 'c' 之間最長的匹配。
# 你可以使用變量代替 substring。
X='a*C'
echo ${stringZ#$X} # 123ABCabc
echo ${stringZ##$X} # abc
# 同上。
${string%substring}
刪除從 $string
結尾部分起,匹配到的最短的 $substring
例如:
# 將當前目錄下所有后綴名為 "TXT" 的文件改為 "txt" 后綴。
# 例如 "file1.TXT" 改為 "file1.txt"。
SUFF=TXT
suff=txt
for i in $(ls *.$SUFF)
do
mv -f $i $(i%.$SUFF).$suff
# 除了從變量 $i 右側匹配到的最短的字符串之外,
#+ 其他一切都保持不變。
done ### 如果需要,循環可以壓縮成一行的形式。
${string%%substring}
刪除從 $string
結尾部分起,匹配到的最長的 $substring
stringZ=abcABC123ABCabc
# || 最短
# |------------| 最長
echo ${stringZ%b*c} # abcABC123ABCa
# 從結尾處刪除 'b' 與 'c' 之間最短的匹配。
echo ${stringZ%%b*c} # a
# 從結尾處刪除 'b' 與 'c' 之間最長的匹配。
這個操作對生成文件名非常有幫助
樣例-3. 改變圖像文件的格式及文件名
#!/bin/bash
# cvt.sh:
# 將目錄下所有的 MacPaint 文件轉換為 "pbm" 格式。
# 使用由 Brian Henderson (bryanh@giraffe-data.com) 維護的
#+ "netpbm" 包下的 "macptobpm" 二進制工具。
# Netpbm 是大多數 Linux 發行版的標準組成部分。
OPERATION=macptopbm
SUFFIX=pbm # 新的文件名后綴。
if [ -n "$1" ]
then
directory=$1 # 如果已經通過腳本參數傳入了目錄名的情況……
else
directory=$PWD # 否則就使用當前工作目錄。
fi
# 假設目標目錄下的所有 MacPaint 圖像文件都擁有
#+ ".mac" 的文件后綴名。
for file in $directory/* # 文件名匹配。
do
filename=${file%.*c} # 從文件名中刪除 ".mac" 后綴
#+ ('.*c' 匹配 '.' 與 'c' 之間的
# 所有字符,包括其本身)。
$OPERATION $file > "$filename.$SUFFIX"
# 將轉換結果重定向到新的文件。
rm -f $file # 在轉換后刪除原文件。
echo "$filename.$SUFFIX" # 將記錄輸出到 stdout 中。
done
exit 0
# 練習:
# -----
# 這個腳本會將當前工作目錄下的所有文件進行轉換。
# 修改腳本,使得它僅轉換 ".mac" 后綴的文件。
# *** 還可以使用另外一種方法。 *** #
#!/bin/bash
# 將圖像批處理轉換成不同的格式。
# 假設已經安裝了 imagemagick。(在大部分 Linux 發行版中都有)
INFMT=png # 可以是 tif, jpg, gif 等等。
OUTFMT=pdf # 可以是 tif, jpg, gif, pdf 等等。
for pic in *"$INFMT"
do
p2=$(ls "$pic" | sed -e s/\.$INFMT//)
# echo $p2
convert "$pic" $p2.$OUTFMT
done
exit $?
樣例-4. 將流音頻格式轉換成 ogg 格式
#!/bin/bash
# ra2ogg.sh: 將流音頻文件 (*.ra) 轉換成 ogg 格式。
# 使用 "mplayer" 媒體播放器程序:
# http://www.mplayerhq.hu/homepage
# 使用 "ogg" 庫與 "oggenc":
# http://www.xiph.org/
#
# 腳本同時需要安裝一些解碼器,例如 sipr.so 等等一些。
# 這些解碼器可以在 compat-libstdc++ 包中找到。
OFILEPREF=${1%%ra} # 刪除 "ra" 后綴。
OFILESUFF=wav # wav 文件后綴。
OUTFILE="$OFILEPREF""$OFILESUFF"
E_NOARGS=85
if [ -z "$1" ] # 必須指定一個文件進行轉換。
then
echo "Usage: `basename $0` [filename]"
exit $E_NOAGRS
fi
######################################################
mplayer "$1" -ao pcm:file=$OUTFILE
oggenc "$OUTFILE" # 由 oggenc 自動加上正確的文件后綴名。
######################################################
rm "$OUTFILE" # 立即刪除 *.wav 文件。
# 如果你仍需保留原文件,注釋掉上面這一行即可。
exit $?
# 注意:
# -----
# 在網站上,點擊一個 *.ram 的流媒體音頻文件
#+ 通常只會下載到 *.ra 音頻文件的 URL。
# 你可以使用 "wget" 或者類似的工具下載 *.ra 文件本身。
# 練習:
# -----
# 這個腳本僅僅轉換 *.ra 文件。
# 修改腳本增加適應性,使其可以轉換 *.ram 或其他文件格式。
#
# 如果你非常有熱情,你可以擴展這個腳本使其
#+ 可以自動下載并且轉換流媒體音頻文件。
# 給定一個 URL,自動下載流媒體音頻文件 (使用 "wget"),
#+ 然后轉換它。
下面是使用字符串截取結構對 getopt
的一個簡單模擬
樣例-5. 模擬 getopt
#!/bin/bash
# getopt-simple.sh
# 允許在高級腳本編程指南中使用。
getopt_simple()
{
echo "getopt_simple()"
echo "Parameters are '$*'"
until [ -z "$1" ]
do
echo "Processing parameter of: '$1'"
if [ ${1:0:1} = '/' ]
then
tmp=${1:1} # 刪除開頭的 '/'
parameter=${tmp%%=*} # 取出名稱。
value=${tmp##*=} # 取出值。
echo "Parameter: '$parameter', value: '$value'"
eval $parameter=$value
fi
shift
done
}
# 將所有參數傳遞給 getopt_simple()。
getopt_simple $*
echo "test is '$test'"
echo "test2 is '$test2'"
exit 0 # 可以查看該腳本的修改版 UseGetOpt.sh。
---
sh getopt_example.sh /test=value1 /test2=value2
Parameters are '/test=value1 /test2=value2'
Processing parameter of: '/test=value1'
Parameter: 'test', value: 'value1'
Processing parameter of: '/test2=value2'
Parameter: 'test2', value: 'value2'
test is 'value1'
test2 is 'value2'
子串替換
${string/substring/replacement}
替換匹配到的第一個 replacement
${string//substring/replacement}
替換匹配到的所有 replacement
stringZ=abcABC123ABCabc
echo ${stringZ/abc/xyz} # xyzABC123ABCabc
# 將匹配到的第一個 'abc' 替換為 'xyz'。
echo ${stringZ//abc/xyz} # xyzABC123ABCxyz
# 將匹配到的所有 'abc' 替換為 'xyz'。
echo ---------------
echo "$stringZ" # abcABC123ABCabc
echo ---------------
# 字符串本身并不會被修改!
# 匹配以及替換的字符串可以是參數么?
match=abc
repl=000
echo ${stringZ/$match/$repl} # 000ABC123ABCabc
# ^ ^ ^^^
echo ${stringZ//$match/$repl} # 000ABC123ABC000
# Yes! ^ ^ ^^^ ^^^
echo
# 如果沒有給定 $replacement 字符串會怎樣?
echo ${stringZ/abc} # ABC123ABCabc
echo ${stringZ//abc} # ABC123ABC
# 僅僅是將其刪除而已。
${string/#substring/replacement}
替換 $string
中最前端匹配到的 $substring
為 $replacement
${string/%substring/replacement}
替換 $string
中最末端匹配到的 $substring
為 $replacement
stringZ=abcABC123ABCabc
echo ${stringZ/#abc/XYZ} # XYZABC123ABCabc
# 將前端的 'abc' 替換為 'XYZ'
echo ${stringZ/%abc/XYZ} # abcABC123ABCXYZ
# 將末端的 'abc' 替換為 'XYZ'
使用 awk 處理字符串
在 Bash 腳本中可以調用字符串處理工具 awk 來替換內置的字符串處理操作
樣例-6. 使用另一種方式來截取和定位子字符串
#!/bin/bash
# substring-extraction.sh
String=23skidoo1
# 012345678 Bash
# 123456789 awk
# 注意不同字符串索引系統:
# Bash 中第一個字符的位置為0。
# Awk 中第一個字符的位置為1。
echo ${String:2:4} # 從第3位開始(0-1-2),4個字符的長度
# skid
# Awk 中與 ${string:pos:length} 等價的是 substr(string,pos,length)。
echo | awk '
{ print substr("'"${String}"'",3,4) # skid
}
'
# 將空的 "echo" 通過管道傳遞給 awk 作為一個模擬輸入,
#+ 這樣就不需要提供一個文件名來操作 awk 了。
echo "----"
# 同樣的:
echo | awk '
{ print index("'"${String}"'", "skid") # 3
} # (skid 從第3位開始)
' # 這里使用 awk 等價于 "expr index"。
exit 0