Shell進階腳本-字符串處理

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
# 現在可以了。
# 括號或者增加空格都可以"轉義"位置參數。

其中,參數 positionlength 可以傳入一個變量而不一定需要傳入常量

樣例-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}
替換匹配到的第一個 substring 為replacement

${string//substring/replacement}
替換匹配到的所有 substring 為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
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,117評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,860評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,128評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,291評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,025評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,421評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,477評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,642評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,177評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,970評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,157評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,717評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,410評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,821評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,053評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,896評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,157評論 2 375

推薦閱讀更多精彩內容