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

概要

linuxなどで使うテキスト編集コマンドの使い方で、これさえ知っていればOKというもののまとめ(vi, sed, grep, sort, uniq, cut, join, tr, iconv, diff)。 後半は特に必要とはいえないため適当。重要なのはvi, sed, grepまでかな。 複数のファイルを扱う場合に使うxargsコマンドについても説明する。 テキスト検索にはripgrep(rg)やfzfなどのモダンなツールもあるが、本記事では基本的なコマンドに絞る。

※(数指定)のついているものは、コマンドの前に数を指定することで指定された数だけ該当する操作を行える。 (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: 単語削除(数指定)
  • yw: 単語コピー(数指定)
  • dd: 行削除(数指定)
  • yy: 行コピー(数指定)
  • p: カーソルの下に貼り付け
  • P: カーソル行に貼り付け
  • s: カーソル位置の文字を修正、上書き(数指定)
  • Ctrl-h: Back Space
  • J: カーソル行と次行を連結する
  • u: 直前の作業の取り消し
  • .: 直前の作業の繰り返し
  • :/文字列: 文字列の検索(/.はエスケープが必要)
  • n: 下方向への検索
  • N: 上方向への検索
  • :%s/文字列1/文字列2/g: ファイル内の文字列1を文字列2に置換(全箇所)
  • :!コマンド: ローカルコマンド実行
  • .swpファイルを生成しない。 :set noswapfile
  • .swpファイルを生成する。 :set swapfile
  • :e ../toedit.txt: 指定したパスのファイルを開く
  • :w!: 保存
  • :q!: 終了
  • :wq!: 保存して終了
  • :w ファイル名: ファイル名として保存

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

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

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

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

指定行の文字列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
bash

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

拡張正規表現

+ ? { } ( ) |
bash

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

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

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

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

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

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

grep パターン infile
bash
  • grep -F: 正規表現が使えず、固定の文字列のみの検索(旧fgrep、POSIXで非推奨のためgrep -Fを使用)
  • grep: 正規表現
  • grep -E: 拡張正規表現(旧egrep、POSIXで非推奨のためgrep -Eを使用)

速度は

(高速) grep -F > grep > grep -E (低速)
bash

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

grep パターン infile > outfile
bash

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

コマンド | grep パターン
bash

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

grep -v パターン infile
bash

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

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

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

  • -i: 大文字小文字を区別しない
  • -n: 行番号を出力する
sed -n -e '/xxx/p'
grep -e 'xxx'
bash

削除

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

2つのリストの共通行を抽出したり、一方にのみ含まれる行を抽出したりする。 よく使うオプション

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

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

grep -F -xf file1 file2
bash

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

cat file1 | tr -d '\r' | grep -F -xf - file2
text

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

grep -F -v -xf file1 file2
bash

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

コマンド | grep -F -xf file1 -
bash

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

コマンド | grep -F -v -xf file1 -
bash

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

コマンド | grep -F -v -xf - file1
bash

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

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

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

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

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

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

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

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

CSVの場合

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

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

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

-k2nr,2のようにオプションを指定する。 固定長フィールド、たとえば以下のようなテキストをPIDの列でソートする場合(ps auxの出力の一部)

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
ps.txt

並べ替えオプションなし、bnの違いは

# 空白文字がソート対象→ソートの意味をなさない
$ 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
shell-session

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

uniq infile
bash

出現頻度のカウント

uniq -c infile
bash

重複行のみ抽出

uniq -d infile
bash

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

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

CSVファイルの指定列を抽出する。 主なオプション

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

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

cut -d',' -f2,5 infile
bash

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
text

2個のCSVを指定フィールドをキーにjoinする。 両方のファイルは結合キーでソート済みである必要がある。

join オプション file1 file2
bash

オプションは

  • -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
bash

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
bash

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

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

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

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

DOSのCR( carriage return、0x0D)を削除

tr -d '\r' < infile > outfile
text

文字コード変換。nkfは多くのモダンなLinuxディストリビューションでデフォルトインストールされていないため、iconvを使う。

iconv -f 入力エンコーディング -t 出力エンコーディング infile > outfile
# 例: Shift_JISからUTF-8へ変換して上書き
find path -type f -name "*.js" | xargs -I{} sh -c "iconv -f SHIFT_JIS -t UTF-8 {} > {}.tmp && mv {}.tmp {}"
bash

主なエンコーディング指定

  • UTF-8
  • EUC-JP
  • Shift_JIS
  • ISO-2022-JP(JIS)

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

diff file1 file2
bash

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

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

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

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

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

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

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

正解

find . -name '*.sql' -print0 | xargs -0  sh -c "sed -e 's/AAA/BBB/g' {} > {}.bak"
text
  • -P 最大プロセス数: ただし-P 0でできるだけ多くのプロセス数
  • -L 各プロセスに与える引数の数: たとえば-L 3では3個ずつ処理する

-Pだけだと必ずしも最大限(のコア数を)使ってくれるわけではない。-Lと併用して強制する。-L 1だと前のコマンドの出力結果を1個ずつ処理することになる。 このあたり詳細は https://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"
text

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

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