99re热视频这里只精品,久久久天堂国产精品女人,国产av一区二区三区,久久久精品成人免费看片,99久久精品免费看国产一区二区三区

Shell 字符串操作

2020-12-24 14:52 更新

前言

忙活了一個禮拜,終于等到周末,可以空下來寫點東西。

之前已經(jīng)完成《數(shù)值運算》《布爾運算》,這次輪到介紹字符串操作 。咱們先得弄明白兩個內(nèi)容:

  • 什么是字符串?
  • 對字符串有哪些操作?

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

字符串:簡稱“串”。有限字符的序列。數(shù)據(jù)元素為字符的線性表,是一種數(shù)據(jù)的邏輯結(jié)構(gòu)。在計算機中可有不同的存儲結(jié)構(gòu)。在串上可進行求子串、插入字符、刪除字符、置換字符等運算。

而字符呢?

字符:計算機程序設(shè)計及操作時使用的符號。包括字母、數(shù)字、空格符、提示符及各種專用字符等。

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

  • 找出字符或者字符串的類型,是數(shù)字、字母還是其他特定字符,是可打印字符,還是不可打印字符(一些控制字符)。

  • 找出組成字符串的字符個數(shù)和字符串的存儲結(jié)構(gòu)(比如數(shù)組)。

  • 對串的常規(guī)操作:求子串、插入字符、刪除字符、置換字符、字符串的比較等。

  • 對串的一些比較復(fù)雜而有趣的操作,這里將在最后介紹一些有趣的范例。

字符串的屬性

字符串的類型

字符有可能是數(shù)字、字母、空格、其他特殊字符,而字符串有可能是它們中的一種或者多種的組合,在組合之后還可能形成具有特定意義的字符串,諸如郵件地址,URL地址等。

范例:數(shù)字或者數(shù)字組合

$ i=5;j=9423483247234;
$ echo $i | grep -q "^[0-9]$"
$ echo $?
0
$ echo $j | grep -q "^[0-9]\+$"
$ echo $?
0

范例:字符組合(小寫字母、大寫字母、兩者的組合)

$ c="A"; d="fwefewjuew"; e="fewfEFWefwefe"
$ echo $c | grep -q "^[A-Z]$"
$ echo $d | grep -q "^[a-z]\+$"
$ echo $e | grep -q "^[a-zA-Z]\+$"

范例:字母和數(shù)字的組合

$ ic="432fwfwefeFWEwefwef"
$ echo $ic | grep -q "^[0-9a-zA-Z]\+$"

范例:空格或者 Tab 鍵等

$ echo " " | grep " "
$ echo -e "\t" | grep "[[:space:]]" #[[:space:]]會同時匹配空格和TAB鍵
$ echo -e " \t" | grep "[[:space:]]"
$ echo -e "\t" | grep "" #為在鍵盤上按下TAB鍵,而不是字符

范例:匹配郵件地址

$ echo "test2007@lzu.cn" | grep "[0-9a-zA-Z\.]*@[0-9a-zA-Z\.]"
test2007@lzu.cn

范例:匹配 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

說明:

  • /dev/null/dev/zero 設(shè)備非常有趣,都猶如黑洞,什么東西掉進去都會消失殆盡;后者還是個能源箱,總能從那里取到0,直到退出
  • [[:space:]]grep 用于匹配空格或 TAB 鍵字符的標記,其他標記請查幫助:man grep
  • 上面都是用 grep 來進行模式匹配,實際上 sed,awk 都可用來做模式匹配,關(guān)于匹配中用到的正則表達式知識,請參考后面的相關(guān)資料
  • 如果想判斷字符串是否為空,可判斷其長度是否為零,可通過 test 命令的 -z 選項來實現(xiàn),具體用法見 test 命令,man test

范例:判斷字符是否為可打印字符

$ echo "\t\n" | grep "[[:print:]]"
\t\n
$ echo $?
0
$ echo -e "\t\n" | grep "[[:print:]]"
$ echo $?
1

字符串的長度

除了組成字符串的字符類型外,字符串還有哪些屬性呢?組成字符串的字符個數(shù)。

下面我們來計算字符串的長度,即所有字符的個數(shù),并簡單介紹幾種求字符串中指定字符個數(shù)的方法。

范例:計算某個字符串的長度

即計算所有字符的個數(shù),計算方法五花八門,擇其優(yōu)著而用之:

$ 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

范例:計算某些指定字符或者字符組合的個數(shù)

$ 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

范例:統(tǒng)計單詞個數(shù)

更多相關(guān)信息見《數(shù)值計算》單詞統(tǒng)計 相關(guān)范例。

$ echo $var | wc -w
5
$ echo "$var" | tr " " "\n" | grep get | uniq -c
1
$ echo "$var" | tr " " "\n" | grep get | wc -l
1

說明:

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

字符串的顯示

接下來討論如何控制字符在終端的顯示。

范例:在屏幕控制字符顯示位置、顏色、背景等

$ echo -e "\033[31;40m" #設(shè)置前景色為黑色,背景色為紅色
$ echo -e "\033[11;29H Hello, World\!" #在屏幕的第11行,29列開始打印字符串Hello,World!

范例:在屏幕的某個位置動態(tài)顯示當前系統(tǒng)時間

$ while :; do echo -e "\033[11;29H "$(date "+%Y-%m-%d %H:%M:%S"); done

范例:過濾掉某些控制字符串

col 命令過濾某些控制字符,在處理諸如 script,screen 等截屏命令的輸出結(jié)果時,很有用。

$ screen -L
$ cat /bin/cat
$ exit
$ cat screenlog.0 | col -b   # 把一些控制字符過濾后,就可以保留可讀的操作日志

字符串的存儲

在我們看來,字符串是一連串的字符而已,但是為了操作方便,我們往往可以讓字符串呈現(xiàn)出一定的結(jié)構(gòu)。在這里,我們不關(guān)心字符串在內(nèi)存中的實際存儲結(jié)構(gòu),僅僅關(guān)系它呈現(xiàn)出來的邏輯結(jié)構(gòu)。比如,這樣一個字符串: get the length of me",我們可以從不同的方面來呈現(xiàn)它。

  • 通過字符在串中的位置來呈現(xiàn)它

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

  • 根據(jù)某個分割符來取得字符串的各個部分

這里最常見的就是行分割符、空格或者 TAB 分割符了,前者用來當行號,我們似乎已經(jīng)司空見慣了,因為我們的編輯器就這樣“莫名”地處理著行分割符(在 UNIX 下為 \\n,在其他系統(tǒng)下有一些不同,比如 Windows 下為 \r\n )。而空格或者 TAB 鍵經(jīng)常用來分割數(shù)據(jù)庫的各個字段,這似乎也是司空見慣的事情。

正因為這樣,所以產(chǎn)生了大量優(yōu)秀的行編輯工具,諸如 grep,awksed 等。在“行內(nèi)”(姑且這么說吧,就是處理單行,即字符串中不再包含行分割符)的字符串分割方面,cutawk 提供了非常優(yōu)越的“行內(nèi)”(處理單行)處理能力。

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

同樣是用到分割符,但為了更方便的操作分割以后的字符串的各個部分,我們抽象了“數(shù)組”這么一個數(shù)據(jù)結(jié)構(gòu),從而讓我們更加方便地通過下標來獲取某個指定的部分。 bash 提供了這么一種數(shù)據(jù)結(jié)構(gòu),而優(yōu)秀的 awk 也同樣提供了它,我們這里將簡單介紹它們的用法。

范例:把字符串拆分成字符串數(shù)組

  • Bash 提供的數(shù)組數(shù)據(jù)結(jié)構(gòu),以數(shù)字為下標的,和 C 語言從 0 開始的下標一樣

$ var="get the length of me"
$ var_arr=($var)    #把字符串var存放到字符串數(shù)組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[@]}   #類似于求字符串長度,`#`操作符也可用來求數(shù)組元素個數(shù)
5

也可以直接給某個數(shù)組元素賦值

$ var_arr[5]="new_element"
$ echo ${var_arr[5]}
6
$ echo ${var_arr[5]}
new_element

Bash 實際上還提供了一種類似于“數(shù)組”的功能,即 for i in,它可以很方便地獲取某個字符串的各個部分,例如:

$ for i in $var; do echo -n $i"_"; done
get_the_length_of_me_
  • awk 里的數(shù)組,注意比較它和 Bash 里的數(shù)組的異同

split 把一行按照空格分割,存放到數(shù)組 var\_arr 中,并返回數(shù)組長度。注意:這里第一個元素下標不是 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 是該行的域的總數(shù),類似于上面數(shù)組的長度,它同樣提供了一種通過類似“下標”訪問某個字符串的功能。

$ 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 的“數(shù)組”功能何止于此呢,看看它的 for 引用吧,注意,這個和 Bash 里頭的 for 不太一樣,i 不是元素本身,而是下標:

$ echo $var | awk '{split($0, var_arr, " "); for(i in var_arr) printf("%s ",var_arr[i]);}'
of me get the length
4 5 1 2 3

另外,從上述結(jié)果可以看到,經(jīng)過 for 處理后,整個結(jié)果沒有按照原理的字符順序排列,不過如果僅僅是迭代出所有元素這個同樣很有意義。

awk 還有更“厲害”的處理能力,它的下標可以不是數(shù)字,可以是字符串,從而變成了“關(guān)聯(lián)”數(shù)組,這種“關(guān)聯(lián)”在某些方面非常方便。比如,把某個文件中的某個系統(tǒng)調(diào)用名根據(jù)另外一個文件中的函數(shù)地址映射表替換成地址,可以這么實現(xiàn):

$ 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函數(shù)刪除某個數(shù)組元素。如果某些場合有需要的話,別忘了awk還支持二維數(shù)組。

字符串常規(guī)操作

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

取子串

取子串的方法主要有:

  • 直接到指定位置求子串
  • 字符匹配求子串

范例:按照位置取子串

比如從什么位置開始,取多少個字符

$ 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

awk$var 按照空格分開為多個變量,依次為 $1,$2,$3$4,$5

$ echo $var | awk '{printf("%s\n", $1);}'
get
$ echo $var | awk '{printf("%s\n", $5);}'
me

差點略掉cut小工具,它用起來和awk類似,-d指定分割符,如同awk-F指定分割符一樣;-f指定“域”,如同awk的$數(shù)字。

$ echo $var | cut -d" " -f 5

范例:匹配字符求子串

用 Bash 內(nèi)置支持求字串:

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

刪除所有 空格+字母組合 的字符串:

$ echo $var | sed 's/ [a-z]*//g'
get
$ echo $var | sed 's/[a-z]* //g'
me

sed 有按地址(行)打印(p)的功能,記得先用 tr 把空格換成行號:

$ echo $var | tr " " "\n" | sed -n 1p
get
$ echo $var | tr " " "\n" | sed -n 5p
me

tr 也可以用來取子串,它可以類似#% 來“拿掉”一些字符串來實現(xiàn)取子串:

$ echo $var | tr -d " "
getthelengthofme
$ echo $var | tr -cd "[a-z]" #把所有的空格都拿掉了,僅僅保留字母字符串,注意-c和-d的用法
getthelengthofme

說明:

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

對于字符串的截取,實際上還有一些命令,如果 head,tail 等可以實現(xiàn)有意思的功能,可以截取某個字符串的前面、后面指定的行數(shù)或者字節(jié)數(shù)。例如:

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

查詢子串

子串查詢包括:

  • 返回符合某個模式的子串本身
  • 返回子串在目標串中的位置

準備:在進行下面的操作之前,請準備一個文件 test.txt,里頭有內(nèi)容 "consists of",用于下面的范例。

范例:查詢子串在目標串中的位置

expr index貌似僅僅可以返回某個字符或者多個字符中第一個字符出現(xiàn)的位置

$ var="get the length of me"
$ expr index "$var" t
3

awk卻能找出字串,match還可以匹配正則表達式

$ echo $var | awk '{printf("%d\n", match($0,"the"));}'
5

范例:查詢子串,返回包含子串的行

awk,sed 都可以實現(xiàn)這些功能,但是 grep 最擅長

$ grep "consists of" test.txt   # 查詢文件包含consists of的行,并打印這些行
$ grep "consists[[:space:]]of" -n -H test.txt # 打印文件名,子串所在行的行號和該行的內(nèi)容
$ grep "consists[[:space:]]of" -n -o test.txt # 僅僅打印行號和匹配到的子串本身的內(nèi)容

$ awk '/consists of/{ printf("%s:%d:%s\n",FILENAME, FNR, $0)}' test.txt  #看到?jīng)]?和grep的結(jié)果一樣
$ sed -n -e '/consists of/=;/consists of/p' test.txt #同樣可以打印行號

說明:

  • awkgrep,sed 都能通過模式匹配查找指定字符串,但它們各有所長,將在后續(xù)章節(jié)中繼續(xù)使用和比較它們,進而發(fā)現(xiàn)各自優(yōu)點
  • 在這里姑且把文件內(nèi)容當成了一個大的字符串,在后面章節(jié)中將專門介紹文件操作,所以對文件內(nèi)容中存放字符串的操作將會有更深入的分析和介紹

子串替換

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

范例:把變量 var 中的空格替換成下劃線

{} 運算符,還記得么?網(wǎng)中人的教程

$ var="get the length of me"
$ echo ${var/ /_}        #把第一個空格替換成下劃線
get_the length of me
$ echo ${var// /_}       #把所有空格都替換成下劃線
get_the_length_of_me

awk,awk 提供了轉(zhuǎn)換的最小替換函數(shù) sub 和全局替換函數(shù) 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'   #看到?jīng)]有,簡短兩個命令就實現(xiàn)了最小匹配和最大匹配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 命令實現(xiàn),它會把文件中所有的行全部倒轉(zhuǎn)過來。在某種意義上來說,排序?qū)嶋H上也是一個字符串替換。

插入子串

在指定位置插入子串,這個位置可能是某個子串的位置,也可能是從某個文件開頭算起的某個長度。通過上面的練習(xí),我們發(fā)現(xiàn)這兩者之間實際上是類似的。

公式:插入子串=把"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怎么用來插入字符吧,因為它的標簽功能很有趣說明:() 將不匹配到的字符串存放為一個標簽,按匹配順序為\1,\2...

$ echo $var | sed -e 's/\( \)/_\1/'
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...,看到?jīng)]?\2\1 調(diào)換位置后,theget 的位置掉換了:

$ echo $var | sed -e 's/\([a-z]*\) \([a-z]*\) /\2 \1 /g'
the get of length me

sed 還有專門的插入指令,ai,分別表示在匹配的行后和行前插入指定字符

$ echo $var | sed '/get/a test'
get the length of me
test
$ echo $var | sed '/get/i test'
test
get the length of me

刪除子串

刪除子串:應(yīng)該很簡單了吧,把子串替換成“空”(什么都沒有)不就變成了刪除么。還是來簡單復(fù)習(xí)一下替換吧。

范例:把 var 字符串中所有的空格給刪除掉。

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

再用 {}

$ 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

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

子串比較

這個很簡單:還記得 test 命令的用法么? man test 。它可以用來判斷兩個字符串是否相等。另外,有發(fā)現(xiàn)“字符串是否相等”和“字符串能否跟另外一個字符串匹配 " 兩個問題之間的關(guān)系嗎?如果兩個字符串完全匹配,那么這兩個字符串就相等了。所以呢,上面用到的字符串匹配方法,也同樣可以用到這里。

子串排序

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

$ 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

子串進制轉(zhuǎn)換

如果字母和數(shù)字字符用來計數(shù),那么就存在進制轉(zhuǎn)換的問題。在《數(shù)值計算》一節(jié),已經(jīng)介紹了 bc 命令,這里再復(fù)習(xí)一下。

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

說明: ibase 指定輸入進制,obase 指出輸出進制,這樣通過調(diào)整 ibaseobase,你想怎么轉(zhuǎn)就怎么轉(zhuǎn)啦!

子串編碼轉(zhuǎn)換

什么是字符編碼?這個就不用介紹了吧,看過那些亂七八糟顯示的網(wǎng)頁么?大多是因為瀏覽器顯示時的”編碼“和網(wǎng)頁實際采用的”編碼“不一致導(dǎo)致的。字符編碼通常是指:把一序列”可打印“字符轉(zhuǎn)換成二進制表示,而字符解碼呢則是執(zhí)行相反的過程,如果這兩個過程不匹配,則出現(xiàn)了所謂的”亂碼“。

為了解決”亂碼“問題呢?就需要進行編碼轉(zhuǎn)換。在 Linux 下,我們可以使用 iconv 這個工具來進行相關(guān)操作。這樣的情況經(jīng)常在不同的操作系統(tǒng)之間移動文件,不同的編輯器之間交換文件的時候遇到,目前在 Windows 下常用的漢字編碼是 gb2312,而在 Linux 下則大多采用 utf8

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

字符串操作進階

實際上,在用 Bash 編程時,大部分時間都是在處理字符串,因此把這一節(jié)熟練掌握非常重要。

正則表達式

范例:處理 URL 地址

URL 地址(URL(Uniform Resoure Locator:統(tǒng)一資源定位器)是WWW頁的地址)幾乎是我們?nèi)粘I畹耐姘?,我們已?jīng)到了無法離開它的地步啦,對它的操作很多,包括判斷 URL 地址的有效性,截取地址的各個部分(服務(wù)器類型、服務(wù)器地址、端口、路徑等)并對各個部分進行進一步的操作。

下面我們來具體處理這個URL地址:ftp://anonymous:ftp@mirror.lzu.edu.cn/software/scim-1.4.7.tar.gz

$ url="ftp://anonymous:ftp@mirror.lzu.edu.cn/software/scim-1.4.7.tar.gz"

匹配URL地址,判斷URL地址的有效性

$ echo $url | grep "ftp://[a-z]*:[a-z]*@[a-z\./-]*"

截取服務(wù)器類型

$ 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

范例:匹配某個文件中的特定范圍的行

先準備一個測試文件README

Chapter 7 -- Exercises

7.1 please execute the program: mainwithoutreturn, and print the return value
of it with the command "echo $?", and then compare the return of the printf
function, they are the same.

7.2 it will depend on the exection mode, interactive or redirection to a file,
if interactive, the "output" action will accur after the \n char with the line
buffer mode, else, it will be really "printed" after all of the strings have
been stayed in the buffer.

7.3 there is no another effective method in most OS. because argc and argv are
not global variables like environ.

然后開始實驗,

打印出答案前指定行范圍:第 7 行到第 9 行,剛好找出了第 2 題的答案

$ sed -n 7,9p README
7.2 it will depend on the exection mode, interactive or redirection to a file,
if interactive, the "output" action will accur after the \n char with the line
buffer mode, else, it will be really "printed" after all of the strings have

其實,因為這個文件內(nèi)容格式很有特色,有更簡單的辦法

$ awk '/7.2/,/^$/ {printf("%s\n", $0);}' README
7.2 it will depend on the exection mode, interactive or redirection to a file,
if interactive, the "output" action will accur after the \n char with the line
buffer mode, else, it will be really "printed" after all of the strings have
been stayed in the buffer.

有了上面的知識,就可以非常容易地進行這些工作啦:修改某個文件的文件名,比如調(diào)整它的編碼,下載某個網(wǎng)頁里頭的所有 pdf 文檔等。這些就作為練習(xí)自己做吧。

處理格式化的文本

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

關(guān)于樹狀結(jié)構(gòu)的文本的處理,可以參考我早期寫的另外一篇博客文章:源碼分析:靜態(tài)分析 C 程序函數(shù)調(diào)用關(guān)系圖

實際上,只要把握好特性結(jié)構(gòu)的一些特點,并根據(jù)具體的應(yīng)用場合,處理起來就不會困難。

下面來介紹具體文本的操作,以 /etc/passwd 文件為例。關(guān)于這個文件的幫忙和用法,請通過 man 5 passwd 查看。下面對這個文件以及相關(guān)的文件進行一些有意義的操作。

范例:選取指定列

選取/etc/passwd文件中的用戶名和組ID兩列

$ cat /etc/passwd | cut -d":" -f1,4

選取/etc/group文件中的組名和組ID兩列

$ cat /etc/group | cut -d":" -f1,3

范例:文件關(guān)聯(lián)操作

如果想找出所有用戶所在的組,怎么辦?

$ 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 命令用來連接兩個文件,有點類似于數(shù)據(jù)庫的兩個表的連接。 -t 指定分割符,-1 4 -2 3 指定按照第一個文件的第 4 列和第二個文件的第 3 列,即組 ID 進行連接,-o``1.1,2.1 表示僅僅輸出第一個文件的第一列和第二個文件的第一列,這樣就得到了我們要的結(jié)果,不過,可惜的是,這個結(jié)果并不準確,再進行下面的操作,你就會發(fā)現(xiàn):

$ 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

可以看到這個結(jié)果才是正確的,所以以后使用 join 千萬要注意這個問題,否則采取更保守的做法似乎更能保證正確性,更多關(guān)于文件連接的討論見參考后續(xù)資料。

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

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

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

到這里,需要做一個簡單的練習(xí),如何把按照列對應(yīng)的用戶名和用戶 ID 轉(zhuǎn)換成按照行對應(yīng)的,即把類似下面的數(shù)據(jù):

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

轉(zhuǎn)換成:

$ cat a
root    bin     daemon
0       1       2

并轉(zhuǎn)換回去,有什么辦法呢?記得諸如 tr,paste,split 等命令都可以使用。

參考方法:

  • 正轉(zhuǎn)換:先截取用戶名一列存入文件 user,再截取用戶 ID 存入 id,再把兩個文件用 paste -s 命令連在一起,這樣就完成了正轉(zhuǎn)換
  • 逆轉(zhuǎn)換:先把正轉(zhuǎn)換得到的結(jié)果用 split -1 拆分成兩個文件,再把兩個拆分后的文件用 tr 把分割符 \t 替換成 \n,只有用 paste 命令把兩個文件連在一起,這樣就完成了逆轉(zhuǎn)換。

參考資料

后記

  • 這一節(jié)本來是上個禮拜該弄好的,但是這些天太忙了,到現(xiàn)在才寫好一個“初稿”,等到有時間再補充具體的范例。這一節(jié)的范例應(yīng)該是最最有趣的,所有得好好研究一下幾個有趣的范例。
  • 寫完上面的部分貌似是 1 點多,剛 check 了一下錯別字和語法什么的,再添加了一節(jié),即“字符串的存儲結(jié)構(gòu)”,到現(xiàn)在已經(jīng)快 half past 2 啦,晚安,朋友們。
  • 26 號,添加“子串進制轉(zhuǎn)換”和“子串編碼轉(zhuǎn)換”兩小節(jié)以及一個處理 URL 地址的范例。
以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號