1. 程式人生 > >shell程式設計範例之字串操作[轉]

shell程式設計範例之字串操作[轉]


shell程式設計範例之字串操作

下面是"線上新華字典"的解釋:

字串:
簡稱“串”。有限字元的序列。資料元素為字元的線性表,是一種資料的邏輯結構。在計算機中可有不同的儲存結構。在串上可進行求子串、插入字元、刪除字元、置換字元等運算。



而字元呢?

字元:
計算機程式設計及操作時使用的符號。包括字母、數字、空格符、提示符及各種專用字元等。



照這樣說,之前介紹的數值操作中的數字,邏輯運算中的真假值,都是以字元的形式呈現出來的,是一種特別的字元,對它們的運算只不過是字元操作的特例罷了。而這裡將研究一般字元的運算,它具有非常重要的意義,因為對我們來說,一般的工作都是處理字元而已。這些運算實際上將圍繞上述兩個定義來做。

第一、找出字元或者字串的型別,是數字、字母還是其他特定字元,是可列印字元,還是不可列印字元(一些控制字元)。
第二、找出組成字串的字元個數和字串的儲存結構(比如陣列)。
第三、對串的常規操作:求子串、插入字元、刪除字元、置換字元、字串的比較等。
第四、對串的一些比較複雜而有趣的操作,這裡將在最後介紹一些有趣的範例。

1. 字串的屬性


1.1 字串的型別

字元有可能是數字、字母、空格、其他特殊字元,而字串有可能是它們任何一種或者多種的組合,在組合之後還可能形成一個具有特定意義的字串,諸如郵件地址,URL地址等。

概要示例: 下面我們來看看如何判斷字元的型別。

// 數字或者數字組合(能夠返回結果,即程式退出狀態是0,說明屬於這種型別,反之不然)
$ i=5;j=9423483247234;
$ echo $i | grep [0-9]*
5
$ echo $j | grep [0-9]*
9423483247234
$ echo $j | grep [0-9]* >/dev/null
$ echo $?
0
// 字元組合(小寫字母、大寫字母、兩者的組合)
$ c="A"; d="fwefewjuew"; e="fewfEFWefwefe"
$ echo $c | grep [A-Z]
A
$ echo $d | grep "[a-z]*"
fwefewjuew
$ echo $e | grep "[a-zA-Z]*"
fewfEFWefwefe
// 字母和數字的組合
$ ic="432fwfwefeFWEwefwef"
$ echo $ic | grep "[0-9a-zA-Z]*"
432fwfwefeFWEwefwef
// 空格或者Tab鍵等
$ echo " " | grep " "
 
$ echo -e "/t" | grep "[[:space:]]" #[[:space:]]會同時匹配空格和TAB鍵

$ echo -e " /t" | grep "[[:space:]]"

$ echo -e "/t" | grep "<tab>" #<tab>為在鍵盤上按下TAB鍵,而不是字元<tab>
// 匹配郵件地址
$ echo "

[email protected]" | grep "[0-9a-zA-Z/.]*@[0-9a-zA-Z/.]"
[email protected]
// 匹配URL地址(以http連結為例)
$ echo "http://news.lzu.edu.cn/article.jsp?newsid=10135" | grep "http://[0-9a-zA-Z/./=?]*"
http://news.lzu.edu.cn/article.jsp?newsid=10135



說明:
[1] /dev/null和/dev/zero是非常有趣的兩個裝置,它們都猶如一個黑洞,什麼東西掉進去都會消失殆盡;後者則是一個能源箱,你總能從那裡取到0,直到你退出。兩者的部分用法見:
關於zero及NULL裝置的一些問題

[2] [[:space:]]是grep用於匹配空格或者TAB鍵型別字串的一種標記,其他類似的標記請檢視grep的幫助,man grep。
[3] 上面都是用grep來進行模式匹配,實際上sed, awk都可以用來做模式匹配,關於匹配中用到的正則匹配模式知識,大家可以參考正則匹配模式,更多相關資料請看參考資料。
[4] 如果僅僅想判斷字串是否為空,即判斷字串的長度是否為零,那麼可以簡單的通過test命令的-z選項來判斷,具體用法見test命令,man test.

概要示例: 判斷字元是否可列印?如何控制字元在終端的顯示。

// 用grep判斷某個字元是否為可列印字元
$ echo "/t/n" | grep "[[:print:]]"
/t/n
$ echo $?
0
$ echo -e "/t/n" | grep "[[:print:]]"
$ echo $?
1
// 用echo的-e選項在螢幕控制字元顯示位置、顏色、背景等
$ echo -e "/33[31;40m" #設定前景色為黑色,背景色為紅色
$ echo -e "/33[11;29H Hello, World/!" #在螢幕的第11行,29列開始列印字串Hello,World!
// 在螢幕的某個位置動態顯示當前系統時間
$ while :; do echo -e "/33[11;29H "$(date "+%Y-%m-%d %H:%M:%S"); done
// 用col命令過濾掉某些控制字元,在處理諸如script,screen等截圖命令的輸出結果時,很有用
$ screen -L
$ cat /bin/cat
$ exit
$ cat screenlog.0 | col -b   # 把一些控制字元過濾後,就可以保留可讀的操作日誌



更多關於字元在終端的顯示控制方法,請參考資料[20]和字元顯示例項[21]:用shell實現的一個動態時鐘。

1.2 字串的長度

概要示例: 除了組成字串的字元型別外,字串還有哪些屬性呢?組成字串的字元個數。下面我們來計算字串的長度,即所有字元的個數,並簡單介紹幾種求字串中指定字元個數的方法。

// 計算某個字串的長度,即所有字元的個數[這計算方法是五花八門,擇其優著而用之]
$ var="get the length of me"
$ echo ${var}     # 這裡等同於$var
get the length of me
$ echo ${#var}
20
$ expr length "$var"
20
$ echo $var | awk '{printf("%d/n", length($0));}'
20
$ echo -n $var |  wc -c
20
// 計算某些指定一個字元或者多個字元的個數
$ echo $var | tr -cd g | wc -c
2
$ echo -n $var | sed -e 's/[^g]//g' | wc -c
2
$ echo -n $var | sed -e 's/[^gt]//g' | wc -c
5
// 如果要統計單詞個數,更多相關資訊見《shell程式設計之數值計算》之 _單詞統計_ 例項。
$ echo $var | wc -w
5
$ echo "$var" | tr " " "/n" | grep get | uniq -c
1
$ echo "$var" | tr " " "/n" | grep get | wc -l
1



說明:
${}操作符在Bash裡頭一個“大牛”,能勝任相當多的工作,具體就看看網中人的《shell十三問》之《Shell十三問》之"$(( )) 與 $( ) 還有${ } 差在哪?" 吧。

1.3 字串的儲存

在我們看來,字串是一連串的字元而已,但是為了操作方便,我們往往可以讓字串呈現出一定的結構。在這裡,我們不關心字串在記憶體中的實際儲存結構,僅僅關係它呈現出來的邏輯結構。比如,這樣一個字串:"get the length of me",我們可以從不同的方面來呈現它。

1.3.1 通過字元在串中的位置來呈現它

這樣我們就可以通過指定位置來找到某個子串。這在c語言裡頭通常可以利用指標來做。而在shell程式設計中,有很多可用的工具,諸如expr,awk都提供了類似的方法來實現子串的查詢動作。兩者都幾乎支援模式匹配(match)和完全匹配(index)。這在後面的字串操作中將詳細介紹。

1.3.2 根據某個分割符來取得字串的各個部分

這裡最常見的就是行分割符、空格或者TAB分割符了,前者用來當行號,我們似乎已經司空見慣了,因為我們的編輯器就這樣“莫名”地處理著行分割符(在 unix下為/n,在其他系統下有一些不同,比如windows下為/r/n)。而空格或者TAB鍵經常用來分割資料庫的各個欄位,這似乎也是司空見慣的事情。

正是因為這樣,所以產生了大量優秀的行編輯工具,諸如grep,awk,sed等。在“行內”(姑且這麼說吧,就是處理單行,即字串裡頭不再包含行分割符)的字串分割方面,cut和awk提供了非常優越的“行內”(處理單行)處理能力。

1.3.3 更方便地處理用分割符分割好的各個部分

同樣是用到分割符,但為了更方便的操作分割以後的字串的各個部分,我們抽象了“陣列”這麼一個數據結構,從而讓我們更加方便地通過下標來獲取某個指定的部分。bash提供了這麼一種資料結構,而優秀的awk也同樣提供了它,我們這裡將簡單介紹它們的用法。

概要示例:利用陣列存放"get the length of me"的用空格分開的各個部分。

//1. bash提供的陣列資料結構,它是以數字為下標的,和C語言從0開始的下標一樣
$ var="get the length of me"
$ var_arr=($var)    #這裡把字串var存放到字串陣列var_arr中了,預設以空格作為分割符
$ echo ${var_arr[0]} ${var_arr[1]} ${var_arr[2]} ${var_arr[3]} ${var_arr[4]}
get the length of me
$ echo ${var_arr[@]}    #這個就是整個字串所有部分啦,這裡可以用*代替@,下同
get the length of me
$ echo ${#var_arr[@]}    #記得上面求某個字串的長度麼,#操作符,如果想求某個陣列元素的字串長度,那麼就把@換成下標吧
5
// 你也可以直接給某個陣列元素賦值
$ var_arr[5]="new_element"
$ echo ${var_arr[5]}
6
$ echo ${var_arr[5]}
new_element
// bash裡頭實際上還提供了一種類似於“陣列”的功能,即"for i in 用指定分割符分開的字串" 的用法
// 即,你可以很方便的獲取某個字串的某個部分
$  for i in $var; do echo -n $i" "; done;
get the length of me

//2. awk裡頭的陣列,注意比較它和bash提供的陣列的異同
// split把一行按照空格分割,存放到陣列var_arr中,並返回陣列的長度。注意:這裡的第一個元素下標不是0,而是1
$ echo $var | awk '{printf("%d %s/n", split($0, var_arr, " "), var_arr[1]);}'
5 get
// 實際上,上面的操作很類似awk自身的行處理功能:awk預設把一行按照空格分割為多個域,並可以通過$1,$2,$3...來獲取,$0表示整行
// 這裡的NF是該行的域的總數,類似於上面陣列的長度,它同樣提供了一種通過“下標”訪問某個字串的功能
$ echo $var | awk '{printf("%d | %s %s %s %s %s | %s/n", NF, $1, $2, $3, $4, $5, $0);}'
5 | get the length of me | get the length of me
// awk的“陣列”功能何止於此呢,看看它的for引用吧,注意,這個和bash裡頭的for不太一樣,i不是元素本身,而是下標
$ echo $var | awk '{split($0, var_arr, " "); for(i in var_arr) printf("%s ",var_arr);}'
get the length of me
$ echo $var | awk '{split($0, var_arr, " "); for(i in var_arr) printf("%s ",i);}'
1 2 3 4 5
// awk還有更“厲害”的處理能力,它的下標可以不是數字,而可以是字串,從而變成了“關聯”陣列,這種“關聯”的作用在某些方便將讓我們非常方便
// 比如,我們這裡就實現一個非凡的應用,把某個檔案中的某個系統呼叫名替換成地址,如果你真正用起它,你會感慨它的“鬼斧神工”的。
// 這就是我在一個場合最好才發現的隨好的實現方案:有興趣看看awk手冊帖子中我在3樓回覆的例項吧。
$ cat symbol
sys_exit
sys_read
sys_close
$ ls /boot/System.map*
$ awk '{if(FILENAME ~ "System.map") map[$3]=$1; else {printf("%s/n", map[$1])}}' /boot/System.map-2.6.20-16-generic symbol
c0129a80
c0177310
c0175d80
// 另外,awk還支援刪除某個陣列元素,如果你不用了就可以用delete函式給刪除掉。如果某些場合有需要的話,別忘了awk還支援二維陣列。



okay,就介紹到這裡啦。為什麼要介紹這些內容?再接著看下面的內容,你就會發現,那些有些的工具是怎麼產生和發展起來的了,如果累了,看看最後一篇參考資料吧,它介紹了一些linux命令名字的由來,說不定可以幫助你理解本節下面的部分呢。

2. 字串常規操作

字串操作包括取子串、查詢子串、插入子串、刪除子串、子串替換、子串比較、子串排序、子串進位制轉換、子串編碼轉換等。

2.1 取子串

概要示例:取子串的方法主要有:直接到指定位置求子串,字元匹配求子串。

// 按照位置取子串,比如從什麼位置開始,取多少個字元
$ var="get the length of me"
$ echo ${var:0:3}
get
$ echo ${var:(-2)}   # 方向相反呢
me
$ echo `expr substr "$var" 5 3` #記得把$var引起來,否則expr會因為空格而解析錯誤
the
$ echo $var | awk '{printf("%s/n", substr($0, 9, 6))}'
length

// 匹配字元求子串
$ echo ${var%% *} #從右邊開始計算,刪除最左邊的空格右邊的所有字元
get
$ echo ${var% *} #從右邊開始計算,刪除第一個空格右邊的所有字元
get the length of
$ echo ${var##* }  #從左邊開始計算,刪除最右邊的空格左邊的所有字元
me
$ echo ${var#* }  #從左邊開始計算,刪除第一個空格左邊的所有字元
the length of me

$ echo $var | awk '{printf("%s/n", $1);}' # awk把$var按照空格分開為多個變數,依次為$1,$2,$3,$4,$5
get
$ echo $var | awk '{printf("%s/n", $5);}'
me

$ echo $var | cut -d" " -f 5  #差點把cut這個小東西忘記啦,用起來和awk類似, -d指定分割符,如同awk用-F指定分割符一樣,-f指定“域”,如同awk的$數字。

$ echo $var | sed 's/ [a-z]*//g'  #刪除所有 空格+字母串 的字串,所以get後面的全部被刪除了
get
$ echo $var | sed 's/[a-z]* //g'
me

$ echo $var | tr " " "/n" | sed -n 1p #sed有按地址(行)列印(p)的功能,記得先用tr把空格換成行號
get
$ echo $var | tr " " "/n" | sed -n 5p
me

// tr也可以用來取子串哦,它也可以類似#和%來“拿掉”一些字串來實現取子串
$ echo $var | tr -d " "
getthelengthofme
$ echo $var | tr -cd "[a-z]" #把所有的空格都拿掉了,僅僅保留字母字串,注意-c和-d的用法
getthelengthofme



說明:
[1] %和#的區別是,刪除字元的方向不一樣,前者在右,後者在左,%%和%,##和#的方向是前者是最大匹配,後者是最小匹配。(好的記憶方法見網中人的鍵盤記憶法:#$%是鍵盤依次從左到右的三個鍵)
[2] tr的-c選項是complement的縮寫,即invert,而-d選項是刪除的意思,tr -cd "[a-z]"這樣一來就變成保留所有的字母啦。

對於字串的擷取,實際上還有一些命令,如果head,tail等可以實現有意思的功能,可以擷取某個字串的前面、後面指定的行數或者位元組數。例如:

$ echo "abcdefghijk" | head -c 4
abcd
$ echo -n "abcdefghijk" | tail -c 4
hijk



2.2. 查詢子串

概要示例:子串查詢包括:返回符合某個模式的子串本身和返回子串在目標串中的位置。

準備:在進行下面的操作之前,請把http://oss.lzu.edu.cn/blog/blog.php?do_showone/tid_1385.html連結中的內容複製到一個文字text裡頭,用於下面的操作。

// 查詢子串在目標串中的位置
$ var="get the length of me"
$ expr index "$var" t        #貌似僅僅可以返回某個字元或者多個字元中第一個字元出現的位置
3  
$ echo $var | awk '{printf("%d/n", match($0,"the"));}'    #awk卻能找出字串,match還可以匹配正則表示式
5

// 查詢子串,返回包含子串的行(awk,sed都可以實現這些功能,但是grep最擅長)
$ grep "consists of" text   # 查詢text檔案包含consists of的行,並列印這些行
$ grep "consists[[:space:]]of" -n -H text # 列印檔名,子串所在行的行號和該行的內容
$ grep "consists[[:space:]]of" -n -o text # 僅僅列印行號和匹配到的子串本身的內容
$ awk '/consists of/{ printf("%s:%d:%s/n",FILENAME, FNR, $0)}' text  #看到沒?和grep的結果一樣
$ sed -n -e '/consists of/=;/consists of/p' text #同樣可以列印行號



說明:
[1] awk,grep,sed都能通過模式匹配查詢指定的字串,但是它們各有擅長的領域,我們將在後續的章節中繼續使用和比較它們,從而發現各自的優點。
[2] 在這裡我們姑且把檔案內容當成了一個大的字串,在後面的章節中我們將專門介紹檔案的操作,所以對檔案內容中存放字串的操作將會有更深入的分析和介紹。

2.3. 子串替換

子串替換就是把某個指定的子串替換成其他的字串,實際上這裡就蘊含了“插入子串”和“刪除子串”的操作。例如,你想插入某個字串到某個子串之前,就可以把原來的子串替換成”子串+新的字串“,如果想刪除某個子串,就把子串替換成空串。不過有些工具提供了一些專門的用法來做插入子串和刪除子串的操作,所以呆夥還是會專門介紹的。另外,要想替換掉某個子串,一般都是先找到子串(查詢子串),然後再把它替換掉的,實質上很多工具在使用和設計上都體現了這麼一點。

概要示例:下面我們把變數var中的空格替換成下劃線看看。

// 用{}運算子,還記得麼?網中人的教程。
$ var="get the length of me"
$ echo ${var/ /_}        #把第一個空格替換成下劃線
get_the length of me
$ echo ${var// /_}        #把所有空格都替換成了下劃線了
get_the_length_of_me

// 用awk,awk提供了轉換的最小替換函式sub和全域性替換函式gsub,類似/和//
$ echo $var | awk '{sub(" ", "_", $0); printf("%s/n", $0);}'
get_the length of me
$ echo $var | awk '{gsub(" ", "_", $0); printf("%s/n", $0);}'
get_the_length_of_me

// 用sed了,子串替換可是sed的特長
$ echo $var | sed -e 's/ /_/'    #s <= substitude
get_the length of me
$ echo $var | sed -e 's/ /_/g'    #看到沒有,簡短兩個命令就實現了最小匹配和最大匹配g <= global
get_the_length_of_me

// 有忘記tr命令麼?可以用替換單個字元的
$ echo $var | tr " " "_"
get_the_length_of_me
$ echo $var | tr '[a-z]' '[A-Z]'   #這個可有意思了,把所有小寫字母都替換為大寫字母
GET THE LENGTH OF ME



說明:sed還有很有趣的標籤用法呢,下面再介紹吧。

有一種比較有意思的字串替換是,整個檔案行的倒置,這個可以通過tac命令實現,它會把檔案中所有的行全部倒轉過來。在一定意義上來說,排序實際上也是一個字串替換。

2.4. 插入子串

插入子串:就是在指定的位置插入子串,這個位置可能是某個子串的位置,也可能是從某個檔案開頭算起的某個長度。通過上面的練習,我們發現這兩者之間實際上是類似的。

公式:插入子串=把"old子串"替換成"old子串+new子串"或者"new子串+old子串"

概要示例::下面在var字串的空格之前或之後插入一個下劃線

// 用{}
$ var="get the length of me"
$ echo ${var/ /_ }        #在指定字串之前插入一個字串
get_ the length of me
$ echo ${var// /_ }
get_ the_ length_ of_ me
$ echo ${var/ / _}        #在指定字串之後插入一個字串
get _the length of me
$ echo ${var// / _}
get _the _length _of _me

// 其他的還用演示麼?這裡主要介紹sed怎麼用來插入字元吧,因為它的標籤功能很有趣
$ echo $var | sed -e 's//( /)/_/1/' #/(和/)將不匹配到的字串存放為一個標籤,按匹配順序為/1,/2...
get_ the length of me
$ echo $var | sed -e 's//( /)/_/1/g'
get_ the_ length_ of_ me
$ echo $var | sed -e 's//( /)//1_/'
get _the length of me
$ echo $var | sed -e 's//( /)//1_/g'
get _the _length _of _me

// 看看sed的標籤的順序是不是/1,/2....,看到沒?/2和/1掉換位置後,the和get的位置掉換了
$ echo $var | sed -e 's//([a-z]*/) /([a-z]*/) //2 /1 /g'
the get of length me
// sed還有專門的插入指令,a和i,分別表示在匹配的行後和行前插入指定字元
$ echo $var | sed '/get/a test'
get the length of me
test
$ echo $var | sed '/get/i test'
test
get the length of me



2.5. 刪除子串

刪除子串:應該很簡單了吧,把子串替換成“空”(什麼都沒有)不就變成了刪除麼。還是來簡單複習一下替換吧。

概要示例::把var字串中所有的空格給刪除掉。

鼓勵: 這樣一替換不知道變成什麼單詞啦,誰認得呢?但是中文卻是連在一起的,所以中文有多難,你想到了麼?原來你也是個語言天才,而英語並不可怕,你有學會它的天賦,只要你有這個打算。

// 再用{}
$ echo ${var// /}
getthelengthofme
// 再用awk
$ echo $var | awk '{gsub(" ","",$0); printf("%s/n", $0);}'
// 再用sed
$ echo $var | sed 's/ //g'
getthelengthofme
// 還有更簡單的tr命令,tr也可以把" "給刪除掉,看
$ echo $var | tr -d " "
getthelengthofme



如果要刪除掉第一個空格後面所有的字串該怎麼辦呢?還記得{}的#和%用法麼?如果不記得,回到這一節的還頭開始複習吧。(實際上刪除子串和取子串未嘗不是兩種互補的運算呢,刪除掉某些不想要的子串,也就同時取得另外那些想要的子串——這個世界就是一個“二元”的世界,非常有趣)

2.6. 子串比較

這個很簡單:還記得test命令的用法麼?man test。它可以用來判斷兩個字串是否相等的。另外,你發現了“字串是否相等”和“字串能否跟另外一個字串匹配"兩個問題之間的關係嗎?如果兩個字串完全匹配,那麼這兩個字串就相等了。所以呢,上面用到的字串匹配方法,也同樣可以用到這裡。

2.7. 子串排序

差點忘記這個重要的內容了,子串排序可是經常用到的,常見的有按字母序、數字序等正序或反序排列。sort命令可以用來做這個工作,它和其他行處理命令一樣,是按行操作的,另外,它類似cut和awk,可以指定分割符,並指定需要排序的列。

$ var="get the length of me"
$ echo $var | tr ' ' '/n' | sort   #正序排
get
length
me
of
the
$ echo $var | tr ' ' '/n' | sort  -r #反序排
the
of
me
length
get
$ cat data.txt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
41 45 44 44 26 44 42 20 20 38 37 25 45 45 45
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
44 20 30 39 35 38 38 28 25 30 36 20 24 32 33
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
41 33 51 39 20 20 44 37 38 39 42 40 37 50 50
46 47 48 49 50 51 52 53 54 55 56
42 43 41 42 45 42 19 39 75 17 17
$ cat data.txt | sort -k 2 -n
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
44 20 30 39 35 38 38 28 25 30 36 20 24 32 33
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
41 33 51 39 20 20 44 37 38 39 42 40 37 50 50
42 43 41 42 45 42 19 39 75 17 17
41 45 44 44 26 44 42 20 20 38 37 25 45 45 45
46 47 48 49 50 51 52 53 54 55 56



2.7. 子串進位制轉換

如果字母和數字字元用來計數,那麼就存在進位制轉換的問題。在數值計算一節的回覆資料裡,我們已經介紹了bc命令,這裡再簡單的複習一下。

$ echo "ibase=10;obase=16;10" | bc
A



說明:ibase指定輸入進位制,obase指出輸出進位制,這樣通過調整ibase和obase,你想怎麼轉就怎麼轉啦!

2.7. 子串編碼轉換

什麼是字元編碼?這個就不用介紹了吧,看過那些亂七八糟顯示的網頁麼?大多是因為瀏覽器顯示時的”編碼“和網頁實際採用的”編碼“不一致導致的。字元編碼通常是指把一序列”可列印“字元轉換成二進位制表示,而字元解碼呢則是執行相反的過程,如果這兩個過程不匹配,則出現了所謂的”亂碼“。

為了解決”亂碼“問題呢?就需要進行編碼轉換。在linux下,我們可以使用iconv這個工具來進行相關操作。這樣的情況經常在不同的作業系統之間移動檔案,不同的編輯器之間交換檔案的時候遇到,目前在windows下常用的漢字編碼是gb2312,而在linux下則大多采用utf8。

$ nihao_gb2312=$(echo "你好" | iconv -f utf8 -t gb2312)
$ echo $nihao_gb2312
????
$ nihao_utf8=$(echo $nihao_gb2312 | iconv -f gb2312 -t utf8)
$ PS1="$ "
$ echo $nihao_utf8
你好



說明:我的終端預設編碼是utf8,所以結果如上。

3. 字串操作範例

實際上,在用Bash程式設計時,大部分時間都是在處理字串,因此把這一節熟練掌握非常重要。

3.1 處理一個非常有意義的字串:URL地址

範例演示:處理URL地址

URL 地址(URL(Uniform Resoure Locator:統一資源定位器)是WWW頁的地址)幾乎是我們日常生活的玩伴,我們已經到了無法離開它的地步啦,對它的操作很多,包括判斷URL地址的有效性,擷取地址的各個部分(伺服器型別、伺服器地址、埠、路徑等)並對各個部分進行進一步的操作。

下面我們來具體處理這個URL地址:
ftp://anonymous:[email protected]/software/scim-1.4.7.tar.gz

$ url="ftp://anonymous:[email protected]/software/scim-1.4.7.tar.gz"
// 匹配URL地址,判斷URL地址的有效性
$ echo $url | grep "ftp://[a-z]*:[a-z]*@[a-z/./-]*"
// 擷取伺服器型別
$ echo ${url%%:*}
ftp
$ echo $url | cut -d":" -f 1
ftp
// 擷取域名
$ tmp=${url##*@} ; echo ${tmp%%/*}
mirror.lzu.edu.cn
// 擷取路徑
$ tmp=${url##*@} ; echo ${tmp%/*}
mirror.lzu.edu.cn/software
// 擷取檔名
$ basename $url
scim-1.4.7.tar.gz
$ echo ${url##*/}
scim-1.4.7.tar.gz
// 擷取檔案型別(副檔名)
$ echo $url | sed -e 's/.*[0-9]./(.*/)//1/g'
tar.gz



有了上面的知識,我們就可以非常容易地進行這些工作啦:修改某個檔案的檔名,比如調整它的編碼,下載某個網頁裡頭的所有pdf文件等。這些就作為練習自己做吧,如果遇到問題,可以在回帖交流。相應地可以參考這個例子:

[1] 用指令碼下載某個網頁中的英文原著(pdf文件)
http://oss.lzu.edu.cn/blog/blog.php?do_showone/tid_1228.html

3.2 處理格式化的文字:/etc/passwd

平時做工作,大多數時候處理的都是一些“格式化”的文字,比如類似/etc/passwd這樣的有固定行和列的文字,也有類似tree命令輸出的那種具有樹形結構的文字,當然還有其他具有特定結構的文字。

關於樹狀結構的文字的處理,可以考慮看看這兩個例子:
[1] 用AWK轉換樹形資料成關係表
http://oss.lzu.edu.cn/blog/blog.php?do_showone/tid_1260.html
[2] 用Graphviz進行視覺化操作──繪製函式呼叫關係圖
http://oss.lzu.edu.cn/blog/blog.php?do_showone/tid_1425.html
實際上,只要把握好特性結構的一些特點,並根據具體的應用場合,處理起來就不會困難。

下面我們來介紹具體有固定行和列的文字的操作,以/etc/passwd檔案為例。關於這個檔案的幫忙和使用者,請通過man 5 passwd檢視。下面我們對這個檔案以及相關的檔案進行一些有意義的操作。

// 選取/etc/passwd檔案中的使用者名稱和組ID兩列
$ cat /etc/passwd | cut -d":" -f1,4
// 選取/etc/group檔案中的組名和組ID兩列
$ cat /etc/group | cut -d":" -f1,3
// 如果想找出所有使用者所在的組,怎麼辦?
$ join -o 1.1,2.1 -t":" -1 4 -2 3 /etc/passwd /etc/group
root:root
bin:bin
daemon:daemon
adm:adm
lp:lp
pop:pop
nobody:nogroup
falcon:users
// 先解釋一下:join命令用來連線兩個檔案,有點類似於資料庫的兩個表的連線。-t指定分割符,"-1 4 -2 3"指定按照第一個檔案的第4列和第二個檔案的第3列,即組ID進行連線,"-o 1.1,2.1"表示僅僅輸出第一個檔案的第一列和第二個檔案的第一列,這樣就得到了我們要的結果,不過,可惜的是,這個結果並不準確,再進行下面的操作,你就會發現:
$ cat /etc/passwd | sort -t":" -n -k 4 > /tmp/passwd
$ cat /etc/group | sort -t":" -n -k 3 > /tmp/group
$ join -o 1.1,2.1 -t":" -1 4 -2 3 /tmp/passwd /tmp/group
halt:root
operator:root
root:root
shutdown:root
sync:root
bin:bin
daemon:daemon
adm:adm
lp:lp
pop:pop
nobody:nogroup
falcon:users
games:users
// 可以看到這個結果才是正確的,所以以後使用join千萬要注意這個問題,否則採取更保守的做法似乎更能保證正確性,更多關於檔案連線的討論見參考資料[14]



上面涉及到了處理某格式化行中的指定列,包括擷取(如SQL的select用法),連線(如SQL的join用法),排序(如SQL的order by用法),都可以通過指定分割符來拆分某個格式化的行,另外,“擷取”的做法還有很多,不光是cut,awk,甚至通過IFS指定分割符的read命令也可以做到,例如:

$ IFS=":"; cat /etc/group | while read C1 C2 C3 C4; do echo $C1 $C3; done


因此,熟悉這些用法,我們的工作將變得非常靈活有趣。

到這裡,需要做一個簡單的練習,如何把按照列對應的使用者名稱和使用者ID轉換成按照行對應的,即把類似下面的資料:

$ cat /etc/passwd | cut -d":" -f1,3 --output-delimiter=" "
root 0
bin 1
daemon 2


轉換成:

$ cat a
root    bin     daemon 
0       1       2


並轉換回去,有什麼辦法呢?記得諸如tr,paste,split等命令都可以使用。

參考方法:
*正轉換:先擷取使用者名稱一列存入檔案user,再擷取使用者ID存入id,再把兩個檔案用paste -s命令連在一起,這樣就完成了正轉換。
*逆轉換:先把正轉換得到的結果用split -1拆分成兩個檔案,再把兩個拆分後的檔案用tr把分割符"/t"替換成"/n",只有用paste命令把兩個檔案連在一起,這樣就完成了逆轉換。

更多有趣的例子,可以參考該序列第一部分的回覆,即參考資料[16]的回覆,以及蘭大開源社群映象站用的映象指令碼,即參考資料[17],另外,參考資料[18]關於用Shell實現一個五筆反查小工具也值得閱讀和改進。

*更多例子將逐步補充和完善。

參考和推薦資料:

[1] 《高階Bash指令碼程式設計指南》之操作字串
http://www.linuxpk.com/doc/abs/string-manipulation.html
[2] 《高階Bash指令碼程式設計指南》之指定變數的型別
http://www.linuxpk.com/doc/abs/declareref.html
[3] 《Shell十三問》之$(( )) 與 $( ) 還有${ } 差在哪?
http://bbs.chinaunix.net/viewthread.php?tid=218853&extra=&page=7#pid1617953
[4] Regular Expressions - User guide
http://www.zytrax.com/tech/web/regex.htm
[5] Regular Expression Tutorial
http://analyser.oli.tudelft.nl/regex/index.html.en
[6] Grep Tutorial
http://www.panix.com/~elflord/unix/grep.html
[7] Sed Tutorial
http://www.panix.com/~elflord/unix/sed.html
[8] awk Tutorial
http://www.gnulamp.com/awk.html
[9] sed Tutorial
http://www.gnulamp.com/sed.html
[10] An awk Primer
http://www.vectorsite.net/tsawk.html
[11] 一些奇怪的 unix 指令名字的由來
http://www.linuxsir.org/bbs/showthread.php?t=24264
[12] 磨練構建正則表示式模式的技能
http://www.ibm.com/developerworks/cn/aix/library/au-expressions.html
[13] 實用正則表示式
http://www.linuxlong.com/forum/bbs-27-1.html
[14] AWK使用手冊 3 樓的回覆帖
http://oss.lzu.edu.cn/modules/newbb/viewtopic.php?topic_id=1006&forum=26
[15] 基礎11:檔案分類、合併和分割(sort,uniq,join,cut,paste,split)
http://blog.chinaunix.net/u/9465/showart_144700.html
[16] Shell程式設計範例之數值運算
http://oss.lzu.edu.cn/blog/blog.php?do_showone/tid_1391.html
[17] 蘭大Mirror映象站的映象指令碼
http://oss.lzu.edu.cn/blog/article.php?tid_1236.html
[18] 一個用Shell寫的五筆反查小工具
http://oss.lzu.edu.cn/blog/blog.php?/do_showone/tid_1017.html
[19] 使用Linux 文字工具簡化資料的提取 
http://linux.chinaunix.net/docs/2006-09-22/2803.shtml
[20] 如何控制終端:游標位置,字元顏色,背景,清屏...
http://oss.lzu.edu.cn/modules/newbb/viewtopic.php?topic_id=962&forum=13
[21] 在終端動態顯示時間
http://oss.lzu.edu.cn/modules/newbb/viewtopic.php?topic_id=964&forum=26

相關推薦

shell程式設計範例字串操作[]

shell程式設計範例之字串操作下面是"線上新華字典"的解釋: 字串:簡稱“串”。有限字元的序列。資料元素為字元的線性表,是一種資料的邏輯結構。在計算機中可有不同的儲存結構。在串上可進行求子串、插入字元、刪除字元、置換字元等運算。 而字元呢? 字元:計算機程式設計及操作時使用的符號。包括字母、數字、空格符、

[授權發表]Shell程式設計範例檔案操作

前言 這一週我們來探討檔案操作。 在日常學習和工作中,我們總是在不斷地和各種檔案打交道,這些檔案包括普通的文字檔案,可以執行的程式檔案,帶有控制字元的文件、存放各種檔案的目錄檔案、網路套接字檔案、裝置檔案等。這些檔案又具有諸如屬主、大小、建立和修

shell程式設計範例程序操作

by falcon<[email protected]> 2008-02-21     這一小節寫了很久,到現在才寫完。本來關注的內容比較多,包括程式開發過程的細節、ELF格式的分析、程序的記憶體映像等,後來搞得“雪球越滾越大”,甚至 脫離了shell

shell程式設計範例檔案系統操作

前言     準備了很久,找了好多天的資料,還不知道應該如何開始動筆寫:因為擔心“拿捏”不住,所以一方面繼續查詢資料,一方面思考如何來寫。作為“shell程式設計 範例”序列的一部分,希望它能夠很好地幫助shell程式設計師理解如何用shell命令來完成和Linux系統

shell指令碼字串操作

1.取字元長度 var="abcdef" echo ${#var} echo ${var} | awk '{print length($0)}' echo ${var} | awk -F "" '{print NF}' echo `expr length ${v

python基礎資料型別字串操作

  1.字串切片ps:字串是不可變的物件, 所以任何操作對原字元 是不會有任何影響的 s1 = "python最簡潔" print(s1[0]) print(s1[1]) print(s1[2]) print(s1[3]) print(s1[4]) print(s1[5]) print(s

python 資料型別字串操作

# s1 = 'hello' # s2 = 'world' # # 可以使用'+'將字串拼接在一起 # s3 = s1 + s2 # print(s3)#helloworld # '*'可以重複前面的字串若干次 # s4 = 'abc' * 3 # print(s4)#連續列印abc三次不換行abca

python 基礎資料結構字串操作

#切割部分s = 'I love you more than i can say' # 切割字串 # sep:指定按照什麼進行切割,預設按照空格切割 # maxsplit:指定最大切割次數,預設不限制次數 # ret = s.split(sep='abc', maxsplit=1) # 從右邊進行切割

驅動開發字串操作

WDM驅動程式可以使用下面4種格式的字串: Unicode串,由UNICODE_STRING結構描述,包含16位字元。Unicode有足夠的程式碼空間編碼地球上所有語言的字型。ANSI串,由ANSI_STRING結構描述,包含8位字元。另一種OEM_STRING串與其相似

shell命令列檔案操作

【檔案操作】 1、反選刪除檔案 先執行:shopt -s extglob 再執行:rm -rf !(file1) rm -rf !(file1|file2) 2、清空檔案內容 :> file 【磁碟管理】 1、檢視當前目錄下個檔

19:字串移位包含問題(1.7程式設計基礎字串)

19:字串移位包含問題 總時間限制: 1000ms 記憶體限制: 65536kB 描述 對於一個字串來說,定義一次迴圈移位操作為:將字串的第一個字元移動到末尾形成新的字串。 給定兩個字串s1和s2,要求判定其中一個字串是否是另一字串通過若干次迴圈移位後的

C++/C程式設計指南基本語句()

運算子的優先順序:一元運算子+ - *的優先順序高於對應的二元運算子   【規則 4-1-1】 如果程式碼行中的運算子比較多,用括號確定表示式的操作順序,避免使用預設的優先順序。   複合表示式:如a = b = c = 0   存在的理由1)書寫簡潔;2)可以提高編譯效率。

Openjudge NOI題庫1.7程式設計基礎字串 34:迴文子串

 總時間限制: 1000ms 記憶體限制: 65536kB 描述 給定一個字串,輸出所有長度至少為2的迴文子串。 迴文子串即從左往右輸出和從右往左輸出結果是一樣的字串,比如:abba,cccdeedccc都是迴文字串。 輸入一個字串,由字母或數字組成。長度500以內。

C/C++程式設計IP地址整數

/* 功能:將輸入的string型別的IP資訊轉換為string型別  * 輸入:string型別的IP資訊  * 輸出:DWORD結果,正常返回解析結果值,異常時,dwIP為0  * 返回:返回解析

R語言開發字串操作基礎瞭解下

在R中的單引號或雙引號中寫入的任何值都將被視為字串,並且在R內部將每個字串儲存在雙引號內,即使我們是使用單引號建立它們。來看下字串構造的規則: 字串開頭和結尾的引號應為雙引號或雙引號,他們不能混合。 雙引號可以插入到以單引號開始和結尾的字串中。 單引號可以插入到以雙引號

python2字串操作

    更新一篇python字串操作函式,未經允許切勿擅自轉載。字串拼接:a+b程式碼:a = "woshi" b = "carcar96" print a+b #方法1 print "==%s=="%(a+b) #方法2執行結果:獲取

awk字串操作字串連結、傳入傳出shell變數) awk 字串連線操作(字串數字,數字字串) awk當中使用外部變數 awk中使用shell的環境變數 awk如何向shell傳值

1.awk基礎 awk的環境變數及其意義   https://blog.csdn.net/snowpay/article/details/52451718 linux awk命令詳解 https://www.cnblogs.com/xudong-bupt/p/3721210.html 2.aw

linux基本操作---shell程式設計 while迴圈

java中我們經常用到while迴圈,那麼shell中也有while迴圈: while簡單迴圈 直接看demo: demo1: 求1-10的和 [[email protected]02 software]# cat while1to10.sh

linux shell程式設計學習--字串的使用和操作

在bash shell的使用過程中,經常會遇到一些字串string的操作,下面是個人的一些使用總結。 一、字串的定義 用雙引號,單引號,或者直接在接在=後,都可以定義一個字串,如下,定義了三個字串 str1="this is a string"

Linux下Shell程式設計算術運算和邏輯比較(數值型別、字串型別、檔案型別)

shell程式設計中的算術運算和邏輯比較,主要根據不同運算物件,採用相應的比較、運算方式。 一、數值型 算術運算 ,以變數自增1為例,+ - * / % 等運算方式同理,運算的方式大體四種: i=`expr $i + 1` let i+=1 , let的操作成員不需要