linuxのテキスト編集(vi, sed, tr, grepなど+xargs)まとめ

linuxなどで使うテキスト編集コマンドの使い方で、これさえ知っていればOKというもののまとめ(vi, sed, grep, sort, uniq, cut, join, tr, nkf, diff)。
後半は特に必要とはいえないため適当。重要なのはvi, sed, grepまでかな。
複数のファイルを扱う場合に使うxargsコマンドについても説明する。

vi

※(数指定)のついているものは、コマンドの前に数を指定することで指定された数だけ該当する操作を行える。
6hで6字分左に移動)

コマンドモードと入力モード

  • コマンドモード→入力モード
    • i: カーソルの直前に挿入(insert)
    • I: カーソル行の最初に挿入
    • a: カーソルの直後に挿入(add)
    • A: カーソル行の最後に挿入
    • o: カーソルの下の行に挿入
    • O: カーソルの上の行に挿入
  • 入力モード→コマンドモード: ESC

移動

(ちょうど右手のホームポジション)

  • h: 左(数指定)
  • j: 下(数指定)
  • k: 上(数指定)
  • l: 右(数指定)

(ページ移動)

  • Ctrl-b: PgUp(back)
  • Ctrl-f: PgDn(forward)

(行内)

  • ^: 行頭(正規表現)
  • $: 行末(正規表現)

(行指定)

  • Ctrl-g: 現在の行表示
  • G: 最終行
  • 数字G: 指定行に移動

コピー、カット、ペースト

  • x: 文字削除(数指定)
  • dw: 単語削除(数指定)
  • dy: 単語コピー(数)
  • dd: 行削除(数指定)
  • yy: 行コピー(数指定)
  • p: カーソルの下に貼り付け
  • P: カーソル行に貼り付け)
  • s: カーソル位置の文字を修正、上書き(数指定)
  • Ctrl-h: Back Space
  • J: カーソル行と次行を連結する

undo,redo

  • u: 直前の作業の取り消し
  • .: 直前の作業の繰り返し

検索、置換

  • :/文字列: 文字列の検索(/.はエスケープが必要)
  • n: 下方向への検索
  • N: 上方向への検索
  • :%s/文字列1/文字列2/g: ファイル内の文字列1を文字列2に置換(全箇所)

便利なコマンド

  • :!コマンド: ローカルコマンド実行

設定

  • .swpファイルを生成しない。
    :set noswapfile
  • .swpファイルを生成する。
    :set swapfile

ファイルを開く

  • e: ../toedit.txt: 指定したパスのファイルを開く

保存、終了

  • :w!: 保存
  • :q!: 終了
  • :wq!: 保存して終了
  • :w ファイル名: ファイル名として保存

sed

行や文字列の置換、削除、抽出を行うコマンド

sed -e コマンド1 -e コマンド2 ... infile > outfile
sed -i -e コマンド1 -e コマンド2 ... infile

置換(sコマンド)

あるファイル内の文字列1をすべて文字列2に置換し、ファイルに出力する

sed -e 's/文字列1/文字列2/g' 置換するファイル > outfile
find . -name \*.html -exec sed -i -e 's/ onkeypress="[^"]*"//g' '{}' \;

指定行の文字列1を文字列2に置換する

sed -e '5s/文字列1/文字列2/g' infile > outfile
sed -e '1,10s/文字列1/文字列2/g' infile > outfile
sed -e '20,$s/文字列1/文字列2/g' infile > outfile

パターンにマッチした行のみ、文字列1を文字列2に置換する

sed -e '/パターン/s/文字列1/文字列2/g' infile > outfile

これらを組み合わせて、例えば1行目から最初の空行までを置換する

sed -e '1,/^$/s/文字列1/文字列2/g' infile > outfile

#ではじまる行以外を置換する

sed -e '/^#/!s/文字列1/文字列2/g' infile > outfile

*.plファイルだけ1行目の/usr/local/bin/usr/binに置換

find ./ -type f -name *.pl | xargs sed -i -e '1s|/usr/local/bin/|/usr/bin/|'

削除(dコマンド)

文字列1を含むすべての行を削除

sed -e '/文字列1/d' infile > outfile

1行目だけ削除して、すべての行頭に""囲みのファイル名を付け、まとめて1個のファイルに出力

ls *csv | xargs -I% sed -e '1d' -e 's/^/"%",/g' % >> a.csv

抽出(pコマンド)

該当する行だけを表示する-nオプションと、
条件にマッチした行のみ取り出すpコマンドをセットで使う。

最終行だけ削除して上書き

sed -i -n -e '$!p' a.csv

ヘッダ付きCSVファイルの結合

sed -n -e '1p' a1.csv >> a_merged.csv
find . -name 'a*.csv' | xargs sed -n -e '2,$p' >> a_merged.csv

便利なオプション

拡張正規表現

+ ? { } ( ) |

を使う場合、-eではなく-Eを指定する。
以下は同じとなる

sed -i -e 's/^#\(.*NOPASSWD: ALL\)/\1/' /etc/sudoers
sed -i -E 's/^#(.*NOPASSWD: ALL)/\1/' /etc/sudoers

sed のコマンドスクリプトを別ファイルに記述しておいて、それを実行する

sed -f スクリプトファイル infile > outfile

参考
http://hydrocul.github.io/wiki/commands/sed.html

grep

ファイルからパターンを検索する。

grep パターン infile

grepの種類

  • fgrep: 正規表現が使えず、固定の文字列のみの検索
  • grep: 正規表現
  • egrep: 拡張正規表現(grep -Eと同じ)

速度は

(高速) fgrep > grep > egrep (低速)

基本的な使い方

infileからパターンを検索し、その含まれる行のみをoutfileに出力する

grep パターン infile > outfile

コマンドの出力結果からパターンの存在する行のみ表示する

コマンド | grep パターン

パターンが含まれない行のみを出力する

grep -v パターン infile

複数のパターンを指定する

grep -e パターン1 -e パターン2 infile > outfile

その他代表的なオプションは

  • -i: 大文字小文字を区別しない
  • -n: 行番号を出力する

条件に合致する行の抽出、削除

抽出

sed -n -e '/xxx/p'
grep -e 'xxx'

削除

sed -e '/xxx/d'
grep -v -e 'xxx'

リストの比較

2つのリストの共通行を抽出したり、一方にのみ含まれる行を抽出したりする。

よく使うオプション

  • -f ファイル: ファイル内の文字列に含まれているかどうかを判定する
  • -x: 文字列ではなく行(\n\nの間)単位で比較する。\rも含まれるため、Windowsで作ったテキストファイルだと比較できないことがあるので事前に除去しておく必要がある

file1とfile2の両方に含まれる行を抽出する(積集合)

fgrep -xf file1 file2

file1に\rが含まれており、file2には含まれない場合

cat file1 | tr -d "\r" | fgrep -xf - file2

file2の中でfile1に含まれない行を抽出する(差集合)

fgrep -v -xf file1 file2

標準入力を使って、コマンドの出力結果の中からfile1にも含まれる行を抽出する(積集合)

コマンド | fgrep -xf file1 - 

ちょっと複雑に
コマンドの出力結果の中からfile1に含まれない行を抽出する(差集合)

コマンド | fgrep -v -xf file1 - 

file1の中からコマンド出力結果に含まれない行を抽出する(差集合・逆パターン)

コマンド | fgrep -v -xf - file1

CSV処理

1列目の値が2か5で、3列目の値が999以外場合の行を抽出(区切り文字はタブ\t

cat infile | egrep '^[25]' | egrep -v '^([^\t]*\t){2}999' > outfile

.*\t(最長マッチ)や.*?\t(最短マッチ)は使えない。

  • 最長マッチだと.*の中に\tを含んで、さらにその先に指定回数の\tまで含んだうえで正規表現全体がマッチングしてしまうため、回数指定の意味がなくなる
  • 最短マッチだと\tまで到達せずにマッチングが終了してしまう

sort

テキストの並べ替えを行う。CSVの指定したフィールドをキーにした並べ替えもできる。

sort オプション ファイル名
コマンド | sort オプション

ソートの仕方に対するオプション

  • -r: 降順でソートする(デフォルトで昇順)
  • -b: フィールド(行)先頭の空白文字を取り除いたうえでソートする。スペースが連続して区切られる固定長フィールドの場合に必要
  • -n: 数字を文字列ではなく数値として扱う。これを指定しないと文字列扱いになる。
  • -f: 大文字小文字を区別しない

CSVの場合

  • -k m(,n): フィールド番号m(からn)をキーにしてソートする。複数指定することも可(前のほうがより優先される)
  • -t 区切り文字: フィールドの区切り文字を指定する。デフォルトの区切り文字は空白文字(ホワイトスペースとタブ、日本語ロケールでは全角スペースも区切り文字扱いになる

-kのフィールド番号に並べ替えオプションを付けることで、フィールドごとにソートの仕方を指定できる。

たとえば

  • 第1ソートキー:2列目を数字(降順)
  • 第2ソートキー:5列目を文字列(昇順)
  • タブ区切り
sort -t$'\t' -k2nr,2 -k5,5 infile

-k2nr,2のようにオプションを指定する。

固定長フィールド、たとえば以下のようなテキストをPIDの列でソートする場合(ps auxの出力の一部)

“`ps.txt
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
root 1 0.0 0.0 19236 516 ? Ss Feb22 0:19 init
root 2 0.0 0.0 0 0 ? S Feb22 0:00 [kthreadd/4433] root 481 0.0 0.0 4068 88 tty1 Ss+ Feb22 0:00 /sbin/mingetty console
root 482 0.0 0.0 4068 8 tty2 Ss+ Feb22 0:00 /sbin/mingetty tty2
user1 1862 0.0 0.0 100496 4584 ? Ss 00:48 0:00 sshd: user1@pts/0
user1 1864 0.0 0.0 108404 2048 pts/0 Ss 00:48 0:00 -bash
root 5667 0.0 0.0 10648 80 ? S<s Jun03 0:00 /sbin/udevd -d
root 5747 0.0 0.0 66240 240 ? Ss Jun03 0:10 /usr/sbin/sshd
root 13857 0.0 0.0 10428 272 ? Ss Feb29 0:00 /usr/sbin/pptpd


並べ替えオプションなし、`b`、`n`の違いは ```shell-session # 空白文字がソート対象→ソートの意味をなさない $ sort -k2,2 ps.txt root 1 0.0 0.0 19236 516 ? Ss Feb22 0:19 init root 2 0.0 0.0 0 0 ? S Feb22 0:00 [kthreadd/4433] root 481 0.0 0.0 4068 88 tty1 Ss+ Feb22 0:00 /sbin/mingetty console root 482 0.0 0.0 4068 8 tty2 Ss+ Feb22 0:00 /sbin/mingetty tty2 USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 5667 0.0 0.0 10648 80 ? S<s Jun03 0:00 /sbin/udevd -d root 5747 0.0 0.0 66240 240 ? Ss Jun03 0:10 /usr/sbin/sshd root 13857 0.0 0.0 10428 272 ? Ss Feb29 0:00 /usr/sbin/pptpd user1 1862 0.0 0.0 100496 4584 ? Ss 00:48 0:00 sshd: user1@pts/0 user1 1864 0.0 0.0 108404 2048 pts/0 Ss 00:48 0:00 -bash # 文字列としての数字がソート対象 $ sort -k2b,2 ps.txt root 1 0.0 0.0 19236 516 ? Ss Feb22 0:19 init root 13857 0.0 0.0 10428 272 ? Ss Feb29 0:00 /usr/sbin/pptpd user1 1862 0.0 0.0 100496 4584 ? Ss 00:48 0:00 sshd: user1@pts/0 user1 1864 0.0 0.0 108404 2048 pts/0 Ss 00:48 0:00 -bash root 2 0.0 0.0 0 0 ? S Feb22 0:00 [kthreadd/4433] root 481 0.0 0.0 4068 88 tty1 Ss+ Feb22 0:00 /sbin/mingetty console root 482 0.0 0.0 4068 8 tty2 Ss+ Feb22 0:00 /sbin/mingetty tty2 root 5667 0.0 0.0 10648 80 ? S<s Jun03 0:00 /sbin/udevd -d root 5747 0.0 0.0 66240 240 ? Ss Jun03 0:10 /usr/sbin/sshd USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND # 数値がソート対象 $ sort -k2n,2 ps.txt USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND root 1 0.0 0.0 19236 516 ? Ss Feb22 0:19 init root 2 0.0 0.0 0 0 ? S Feb22 0:00 [kthreadd/4433] root 481 0.0 0.0 4068 88 tty1 Ss+ Feb22 0:00 /sbin/mingetty console root 482 0.0 0.0 4068 8 tty2 Ss+ Feb22 0:00 /sbin/mingetty tty2 user1 1862 0.0 0.0 100496 4584 ? Ss 00:48 0:00 sshd: user1@pts/0 user1 1864 0.0 0.0 108404 2048 pts/0 Ss 00:48 0:00 -bash root 5667 0.0 0.0 10648 80 ? S<s Jun03 0:00 /sbin/udevd -d root 5747 0.0 0.0 66240 240 ? Ss Jun03 0:10 /usr/sbin/sshd root 13857 0.0 0.0 10428 272 ? Ss Feb29 0:00 /usr/sbin/pptpd

uniq

ソート済みのテキストからユニークな行(、重複している行)を抽出する。
出現頻度のカウントもできる。

uniq infile

出現頻度のカウント

uniq -c infile

重複行のみ抽出

uniq -d infile

ログファイルの指定列の値の出現頻度を上位から順に表示(定番のイディオム)

cat infile | cut -f5 | sort | uniq -c | sort -k1 -r

cut

CSVファイルの指定列を抽出する。

主なオプション

  • -d'区切り文字': 区切り文字を指定。デフォルトの区切り文字はタブ\t
  • -fフィールド番号: 抽出するフィールド番号を指定

カンマ区切りテキストファイルinfileの2列目と5列目を(カンマ区切りで)抽出する

cut -d',' -f2,5 infile

sortコマンドと同様、スペース区切りの場合と固定長フィールドの相性が悪い。
-bフィールドのバイト数でバイト数を指定するか、以下のように連続するスペースをトリミングする。

cat infile | sed -E 's/^\s+//g' | sed -E 's/\s+$//g' | sed -E 's/(\s)+/\1/g' | cut -d' ' -f3-5 > outfile

join

2個のCSVを指定フィールドをキーにjoinする

join オプション file1 file2

オプションは

  • -t区切り文字: 区切り文字を指定
  • -1 フィールド番号: file1のキーになるフィールド番号を指定(1列目の場合不要)
  • -2 フィールド番号: file2のキーになるフィールド番号を指定(1列目の場合不要)
  • -a ファイル番号: 指定した番号のファイルを全行表示する(left join / right join / full join)
  • -o 出力する列: 出力する列を指定する

たとえば

file1の3列目とfile2の1列目をキーにinner joinする

join -1 3 -2 1 file1 file2

file1の2列目とfile2の1列目をキーにleft joinし、
ソートのキーとfile1の3-5列目、file2の1-2列目を表示

join -a 1 -1 3 -2 1 -o 0 1.3 1.4 1.5 2.1 2.2 file1 file2

file1の2列目とfile2の1列目をキーにfull joinする

join -a 1 -a 2 -1 3 -2 1 file1 file2

tr

パターン1をパターン2に置換

tr パターン1 パターン2 < infile > outfile

DOS の CR コードを削除

tr -d '\r' < infile > outfile

nkf

文字コード変換

nkf [option] infile > outfile
find path -type -f -name "*.js" | xargs nkf -w --overwrite
  • --overwrite: ファイルに上書き

出力する漢字コードの指定

  • -j: JIS で出力(デフォルト)
  • -e: EUC で出力
  • -s: Shift-JIS で出力
  • -w: UTF-8 で出力

入力する漢字コードの指定(なくてもよい)

  • -J: JIS で入力
  • -E: EUC で入力
  • -S: Shift-JIS で入力
  • -W: UTF-8 で入力

出力する改行コードの指定(デフォルトでは変換なし)

  • -Lu: LF(Unix)
  • -Lw: CR+LF(Windows)
  • -Lm: CR(Mac)

  • -c: 行末にCRを追加

  • -d: 行末からCRを削除
  • -m: MIME を解読する

diff

2つのテキストファイルの違いを出力する

diff file1 file2

xargsとパイプ

前のコマンドの出力をそのまま後続のコマンドの引数にする。

区切り文字

findからxargsに渡すときはNULL文字を区切り文字として指定をするのが安全。前のコマンドの出力結果に空白文字が含まれるとアウト。

find . -type d -print0 | xargs -0 ls -l

引数文字

-I区切り文字で指定する。デフォルトでは{}

find . -name '*.txt' -print0 | xargs -0 cp {} {}.bak
find . -name '*.txt' -print0 | xargs -0 -I% cp % %.bak

その後にリダイレクトが入ると引数文字が使えなくなる

NGな例

find . -name '*.sql' -print0 | xargs -0 sed -e 's/AAA/BBB/g' {} > {}.bak

正解

find . -name '*.sql' -print0 | xargs -0  sh -c "sed -e 's/AAA/BBB/g' {} > {}.bak"

並列処理

  • -P 最大プロセス数: ただし-P 0でできるだけ多くのプロセス数
  • -L 各プロセスに与える引数の数: たとえば-L 3では3個ずつ処理する

-Pだけだと必ずしも最大限(のコア数を)使ってくれるわけではない。-Lと併用して強制する。-L 1だと前のコマンドの出力結果を1個ずつ処理することになる。
このあたり詳細は
http://d.hatena.ne.jp/pasela/20120122/xargs

find . -name '*.sql' -print0 | xargs -0 -L 1 -P 4 sh -c "sed -e 's/AAA/BBB/g' {} > {}.bak"

デバッグ

-tで実行されるコマンドを(エラー出力で)見ることができる。-L-Pでややこしくなった分割の様子を確認するなどデバッグに。

find . -name '*.sql' -print0 | xargs -0 -t -L 1 -P 4 sh -c "sed -e 's/AAA/BBB/g' {} > {}.bak"