メインコンテンツへ移動

テキスト処理_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
# サイレントマッチング。標準出力には何も出力せず、マッチ成功時に 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

フィールド(列)単位での切り出し。1列を1つのフィールドとして扱い、awk のように特定の列を抽出します:

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

注意:非常に重要な問題は区切り文字です。デフォルトはタブ(Ctrl + v の後に tab)です。-d オプションで他の文字を指定できますが、単一文字しか指定できず、あまり使い勝手が良くありません(複数のスペースがある場合に対応できず、単一文字で区切られたコンテンツにのみ適しています)。

例えば、ps の結果から PID 列と CMD 列を切り出す場合:

# awkなら完璧に解決
ps | awk '{print $1,$4}'
# cutは使いにくい
# デフォルトのタブ区切りでは無効
ps | cut -f 1,4
# スペースを指定しても結果が正しくない
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 の sedGNU 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 ブロックが実行され、次に入力内容から1行ずつ読み込まれ、各パターンブロックが順番に実行されます。すべての内容を読み終えると、最後に 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}'

1行読むごとに 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

通常、次の行を読み込むために使用されます。使い方は以下の通りです:

# 1行目を出力
echo $'1 2\n3 4' | awk 'BEGIN{getline line; print line}'
# 1行目をスキップ(1行目の total xxx を破棄)
ls -l | awk 'BEGIN{getline} {print $0}'

引数なしの getline$0$1, $2... を更新しますが、引数ありの場合は更新しません。例:

# 引数ありの場合はフィールド変数を更新しない
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 は非常に強力になり、ファイルを1行ずつ処理するのが非常に便利になります。

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}'

处理文件内容

1行ずつ読み込む:

# 入力リダイレクト
while read line; do echo $line; done < test.sh
# またはサブシェル
cat test.sh | (while read line; do echo $line; done)

1行の中の各フィールドを読み込む:

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:開始インデックス:長さ} です。開始位置に負の数を指定すると、末尾からの位置になります:

# 最後の2文字を切り出す
field='abcdef'; echo ${field:(-2):2}

P.S. シェルのこれらの文字列処理のサポートは、本当に驚くほど強力です。

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

コメント

コメントはまだありません

コメントを書く