跳到主要內容
黯羽輕揚每天積累一點點

文本_bash筆記4

免費2017-03-26#Tool#grep#sed#cut#awk#Mac sed

文本處理是最最常用的

grep

用於文本搜尋,匹配文件內容,語法格式為:grep pattern filename,例如:

# 找出所有含有for的行
grep 'for' test.sh
# 对多个文件进行搜索
grep 'for' test.sh bak.sh
# 高亮匹配部分
grep 'for' test.sh --color=auto

預設是通配符匹配,正規表達式需要開啟參數E(extended)

# 找出所有以echo开头的行
grep -E '^\s*echo' test.sh

或者使用預設允許正規表達式的egrep命令:

# 同上
egrep '^\s*echo' test.sh

其它選項及特性:

# 只输出匹配部分
grep -o -E '\s[a-zA-Z]\s' test.sh
# 只输出不匹配的行(反选)
grep -v -E '\s[a-zA-Z]\s' test.sh
# 统计匹配行数
grep -c -E '\s[a-zA-Z]\s' test.sh
# 统计匹配项数
grep -o -E '\s[a-zA-Z]\s' test.sh | wc -l
# 输出匹配行及其行号
grep -n -E '\s[a-zA-Z]\s' test.sh
# 输出匹配项所在的文件名(反选是L)
grep -l 'return' test.sh bak.sh return.sh
# 递归搜索目录,输出文件名及行号
grep -n -R  'echo' .
# 忽略大小写
grep -i "ECho" test.sh
# 目录搜索限定文件名格式
# 注意include参数的值必须用引号包起来,与find命令不同
grep -R '=>' . --include '*.jsx}'
# 目录搜索排除特定格式文件名、目录
grep -R '' . --exclude '*.md' --exclude-dir 'node_modules'
# 输出\0作为终结符,一般配合-l只输出文件名,再xargs -0传递给后续命令
grep "echo" . -R -l -Z | xargs ls -l
# 静默匹配,不向stdin输出任何东西,匹配成功返回0
if echo ' abcd' | grep -q -E '^\s*abc'; then echo 'starts with abc'; fi

除了定位匹配項,還可以輸出匹配項的上下文:

# 输出匹配行及后续2行
seq 10 | grep '4' -A 2
# 输出匹配行及之前2行
seq 10 | grep '4' -B 2
# 输出匹配行及前后各2行
seq 10 | grep '4' -C 2

cut

有3種切分方式:-c按字元切分,-f按欄位切分,-b按位元組切分

按字元切分:

# 切出每行第3个字符到第5个字符
echo $'1 2 3 4\n5 6 7 8' | cut -c 3-5
# 第3个字符到行尾
echo $'1 2 3 4\n5 6 7 8' | cut -c 3-
# 第5个字符及之前
echo $'1 2 3 4\n5 6 7 8' | cut -c -5

按欄位(列)切分,把一列當做一個欄位,類似於awk,提取指定的列:

echo $'1 2 3 4\n5 6 7 8' | cut -d ' ' -f 1,3

注意:一個非常重要的問題是分界符,預設是定位字元(Ctrl + vtab),-d選項指定其它字元,只能是單字元,不好用(無法應對多空格的情況,只適用於單字元分隔的內容

例如切出ps結果中的PIDCMD列:

# awk完美解决问题
ps | awk '{print $1,$4}'
# cut不好用
# 默认按制表符cut无效
ps | cut -f 1,4
# 指定空格cut结果不对
ps | cut -d ' ' -f 1,4

按位元組切分,預設會忽略多位元組字元邊界:

# 默认跨字符切分,汉字被切坏了
echo "想做个好人" | cut -b 2-4
# -n选项不分割多字节字符,得到`想`
echo "想做个好人" | cut -n -b 2-4

sed

stream editor,非交互式的編輯器,常用的文本處理工具,最常用的功能是文本替換:

# 删除行开头的空白字符
echo $' \t  我想左对齐' | sed  $'s/^[[:space:]]*\t*//g'

另一個常用功能是檔案原地替換(替換並把結果寫入原檔案):

# 把test.txt里所有的单词替换为[word]
echo $'this is a new file\nnext line' > test.txt
sed -i '' -E 's/[[:alpha:]]{1,}/[word]/g' test.txt

P.S. Mac下sed -i檔案原地替換必須指定備份檔名(雖然可以是空字串),另外,Mac下的sedGUN sed差異非常大,比如沒有+?,沒有\b等等,更多差異請查看Differences between sed on Mac OSX and other “standard” sed?

一般定界符是/,也可以是任意符號:

# 分号
echo $'\t\t\t我想左对齐' | sed $'s;^\t*;;'
# Mac下甚至可以是`|`
echo $'\t\t\t我想左对齐' | sed $'s|^\t*||'
# 没有分界含义的定界符需要转义
echo '&c' | sed -E 's;&[[:alpha:]]{1,}\;;\&;'

其它常用選項:

# /pattern/d删除匹配的行
sed '/^$/d' test.sh
# &表示本次匹配部分
echo 'abc de' | sed -E 's/[[:alpha:]]{1,}/[&]/g'
# \123..反向引用
echo 'aabcc' | sed 's/\([[:alpha:]]\)\1/[\1x2]/g'
# sed 'expr1; expr2...'顺序应用多个正则,效果等价于管道
echo 'aabcc' | sed 's/\([[:alpha:]]\)\1/[\1x2]/g;s/\].*\[/][/'

注意:反向引用例子中的擷取括號必須轉義

awk

通常被用於按列提取,例如:

# 文件名
ps | awk '{print $1, $4}'

非常強大,可以對列和行進行操作,一般格式如下:

awk 'BEGIN{ print "start" } pattern1{ command } END{ print "end" }' file

BEGINEND和模式塊都是可選的,先執行BEGIN塊,然後從輸入內容中讀取一行,依次執行各個模式塊,直到所有內容讀取完畢,然後執行END

pattern也是可選的,不提供表示對每行無條件執行塊中的語句,例如:

# 原样输出
echo $'1 2\n3 4' | awk '{print}'
# 统计行数
echo $'1 2\n3 4' | awk 'BEGIN{lineCount=0} {let lineCount++} END{print lineCount}'

print比較特殊,空格分隔的參數輸出時會被連接起來,逗號分隔參數輸出時會用空格分隔開,例如:

# 输出123
echo '' | awk '{print 1 2 3}'
# 输出1 2 3
echo '' | awk '{print 1,2,3}'
# 输出1-2-3
echo '' | awk '{print 1"-"2"-"3}'

內建變數

awk裡有一些特殊的內建變數:

  • NR:number of records,目前行號

  • NF:number of fields,目前行欄位數

  • $0:目前行文本內容

  • $123...:目前行第n個欄位的文本內容

所以有更簡單的統計行數的方式:

echo $'1 2\n3 4' | awk 'END{print NR}'

每讀一行更新NR,執行到END塊時就是總行數

注意awk裡取變數值不需要透過$取值,無論是內建變數還是自定義變數

傳遞外部變數

awk裡不能直接使用外部變數,需要傳遞進來:

# 输出空
x=3; echo '' | awk '{print x}'
# 输出3
x=3; echo '' | awk -v x=$x '{print x}'

傳遞多個外部變數有更簡單的方式:

# 输出3 4 5
x=3; y=4; z=5; echo '' | awk -v x=$x -v y=$y -v z=$z '{print x,y,z}'
# 简单方式
x=3; y=4; z=5; echo '' | awk '{print x,y,z}' x=$x y=$y z=$z

以鍵值對方式緊跟在語句塊後面,作為命令列參數傳入

getline

一般用來讀取下��行,用法如下:

# 输出第一行
echo $'1 2\n3 4' | awk 'BEGIN{getline line; print line}'
# 跳过第一行(把第一行的total xxx丢弃了)
ls -l | awk 'BEGIN{getline} {print $0}'

不帶參數的getline會更新$0123...(帶參數的不會),例如:

# 带参数的不更新字段变量
echo $'1 2\n3 4' | awk 'BEGIN{print $0; getline line; print $0}'
# 不带参数的会更新字段变量
echo $'1 2\n3 4' | awk 'BEGIN{print $0; getline; print $0}'

執行其它命令

awk中執行其它命令也比較特殊:

# $0是md5 test.sh的输出结果
echo '' | awk '{"md5 test.sh" | getline; print $0}'
# 或者
echo '' | awk '{"md5 test.sh" | getline md5; print md5}'

迴圈、條件

awk中可以使用 C 語言風格的迴圈、條件等結構:

# while循环
seq 10 | awk 'BEGIN{while (getline){print $0}}'
# for循环
seq 10 | awk 'BEGIN{for(i=0; i<10; i++){getline; print $0}}'
# 条件语句
seq 10 | awk 'BEGIN{for(i=0; i<10; i++){getline; if ($1 % 2) {print $0}}}'

這些特性讓awk變得很強大,逐行處理檔案非常便捷

P.S. 更多語句結構,及內建函數請查看man awk

其它選項

常用的選項:

# 指定定界符,默认是空格
echo 'a;b;c' | awk -F ';' '{print $2}'
# 或者
echo 'a;b;c' | awk 'BEGIN{FS=";"} {print $2}'
# 指定输出定界符
echo 'a b c' | awk 'BEGIN{OFS="\t"} {print $1,$2,$3}'
# 模式过滤
# 行号小于2
echo $'1 2\n3 4' | awk 'NR < 2{print $0}'
# 行号在2到4之间
seq 10 | awk 'NR==2,NR==4{print $0}'
# 匹配正则表达式
echo $'1 2\n3 4' | awk '/^3/{print $0}'

處理檔案內容

逐行讀取:

# 输入重定向
while read line; do echo $line; done < test.sh
# 或者子shell
cat test.sh | (while read line; do echo $line; done)

讀取一行中的各個欄位:

line='1 2 3 4'; IFS=' '; for field in $line; do echo $field; done

讀取一個欄位中的每個字元:

field='word'; for ((i=0;i<${#field};i++)) do echo ${field:i:1}; done

這裡用到一個子字串截取的技巧${field:i:1},格式為${var:start_index:length},起點可以是負數,表示倒數:

# 截取最后2个字符
field='abcdef'; echo ${field:(-2):2}

P.S. shell 的這些字串處理支援確實強大到沒朋友

paste

按列拼接文本內容,cat按行拼接,paste可以按列拼接:

seq 3 > no.txt
echo $'吃饭\n睡觉\n打豆豆' > action.txt
# 按行拼接
cat no.txt action.txt
# 按列拼接
paste no.txt action.txt

paste結果如下:

# paste no.txt action.txt | sed -n l
1\t吃饭$
2\t睡觉$
3\t打豆豆$

預設定界符用定位字元,可以用-d選項指定其它定界符:

# 拼接结果用分号分隔
paste -d ';' no.txt action.txt | sed -n l

評論

暫無評論,快來發表你的看法吧

提交評論