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などのモダンなツールもあるが、本記事では基本的なコマンドに絞る。
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: 単語削除(数指定)yw: 単語コピー(数指定)dd: 行削除(数指定)yy: 行コピー(数指定)p: カーソルの下に貼り付けP: カーソル行に貼り付けs: カーソル位置の文字を修正、上書き(数指定)Ctrl-h: Back SpaceJ: カーソル行と次行を連結する
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
行や文字列の置換、削除、抽出を行うコマンド
置換(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に置換する
これらを組み合わせて、例えば1行目から最初の空行までを置換する
#ではじまる行以外を置換する
*.plファイルだけ1行目の/usr/local/binを/usr/binに置換
削除(dコマンド)
文字列1を含むすべての行を削除
1行目だけ削除して、すべての行頭に""囲みのファイル名を付け、まとめて1個のファイルに出力
抽出(pコマンド)
該当する行だけを表示する-nオプションと、
条件にマッチした行のみ取り出すpコマンドをセットで使う。
最終行だけ削除して上書き
ヘッダ付き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 のコマンドスクリプトを別ファイルに記述しておいて、それを実行する
参考 https://hydrocul.github.io/wiki/commands/sed.html
grep
ファイルからパターンを検索する。
grepの種類
grep -F: 正規表現が使えず、固定の文字列のみの検索(旧fgrep、POSIXで非推奨のためgrep -Fを使用)grep: 正規表現grep -E: 拡張正規表現(旧egrep、POSIXで非推奨のためgrep -Eを使用)
速度は
基本的な使い方
infileからパターンを検索し、その含まれる行のみをoutfileに出力する
コマンドの出力結果からパターンの存在する行のみ表示する
パターンが含まれない行のみを出力する
複数のパターンを指定する
その他代表的なオプションは
-i: 大文字小文字を区別しない-n: 行番号を出力する
条件に合致する行の抽出、削除
抽出
削除
リストの比較
2つのリストの共通行を抽出したり、一方にのみ含まれる行を抽出したりする。 よく使うオプション
-f ファイル: ファイル内の文字列に含まれているかどうかを判定する-x: 文字列ではなく行(との間)単位で比較する。CR(0x0D)も含まれるため、Windowsで作ったテキストファイルだと比較できないことがあるので事前に除去しておく必要がある
file1とfile2の両方に含まれる行を抽出する(積集合)
file1にCRが含まれており、file2には含まれない場合
file2の中でfile1に含まれない行を抽出する(差集合)
標準入力を使って、コマンドの出力結果の中からfile1にも含まれる行を抽出する(積集合)
ちょっと複雑に コマンドの出力結果の中からfile1に含まれない行を抽出する(差集合)
file1の中からコマンド出力結果に含まれない行を抽出する(差集合・逆パターン)
CSV処理
1列目の値が2か5で、3列目の値が999以外場合の行を抽出(区切り文字はタブ\t)
.*\t(最長マッチ)や.*?\t(最短マッチ)は使えない。
- 最長マッチだと
.*の中に\tを含んで、さらにその先に指定回数の\tまで含んだうえで正規表現全体がマッチングしてしまうため、回数指定の意味がなくなる - 最短マッチだと
\tまで到達せずにマッチングが終了してしまう
sort
テキストの並べ替えを行う。CSVの指定したフィールドをキーにした並べ替えもできる。
ソートの仕方に対するオプション
-r: 降順でソートする(デフォルトで昇順)-b: フィールド(行)先頭の空白文字を取り除いたうえでソートする。スペースが連続して区切られる固定長フィールドの場合に必要-n: 数字を文字列ではなく数値として扱う。これを指定しないと文字列扱いになる。-f: 大文字小文字を区別しない
CSVの場合
-k m(,n): フィールド番号m(からn)をキーにしてソートする。複数指定することも可(前のほうがより優先される)-t 区切り文字: フィールドの区切り文字を指定する。デフォルトの区切り文字は空白文字(ホワイトスペースとタブ、日本語ロケールでは全角スペースも区切り文字扱いになる)
-kのフィールド番号に並べ替えオプションを付けることで、フィールドごとにソートの仕方を指定できる。
たとえば
- 第1ソートキー:2列目を数字(降順)
- 第2ソートキー:5列目を文字列(昇順)
- タブ区切り
-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
並べ替えオプションなし、b、nの違いは
# 空白文字がソート対象→ソートの意味をなさない
$ 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
ソート済みのテキストからユニークな行(重複している行)を抽出する。 出現頻度のカウントもできる。
出現頻度のカウント
重複行のみ抽出
ログファイルの指定列の値の出現頻度を上位から順に表示(定番のイディオム)
cut
CSVファイルの指定列を抽出する。 主なオプション
-d'区切り文字': 区切り文字を指定。デフォルトの区切り文字はタブ\t-fフィールド番号: 抽出するフィールド番号を指定
カンマ区切りテキストファイルinfileの2列目と5列目を(カンマ区切りで)抽出する
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する。 両方のファイルは結合キーでソート済みである必要がある。
オプションは
-t区切り文字: 区切り文字を指定-1 フィールド番号: file1のキーになるフィールド番号を指定(1列目の場合不要)-2 フィールド番号: file2のキーになるフィールド番号を指定(1列目の場合不要)-a ファイル番号: 指定した番号のファイルを全行表示する(left join / right join / full join)-o 出力する列: 出力する列を指定する
たとえば file1の3列目とfile2の1列目をキーにinner joinする
file1の2列目とfile2の1列目をキーにleft joinし、 ソートのキーとfile1の3-5列目、file2の1-2列目を表示
file1の2列目とfile2の1列目をキーにfull joinする
tr
パターン1をパターン2に置換
DOSのCR( carriage return、0x0D)を削除
iconv
文字コード変換。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 {}"
主なエンコーディング指定
UTF-8EUC-JPShift_JISISO-2022-JP(JIS)
diff
2つのテキストファイルの違いを出力する
xargsとパイプ
前のコマンドの出力をそのまま後続のコマンドの引数にする。
区切り文字
findからxargsに渡すときはNULL文字を区切り文字として指定をするのが安全。前のコマンドの出力結果に空白文字が含まれるとアウト。
引数文字
-I区切り文字で指定する。デフォルトでは{}
find . -name '*.txt' -print0 | xargs -0 cp {} {}.bak
find . -name '*.txt' -print0 | xargs -0 -I% cp % %.bak
その後にリダイレクトが入ると引数文字が使えなくなる NGな例
正解
並列処理
-P 最大プロセス数: ただし-P 0でできるだけ多くのプロセス数-L 各プロセスに与える引数の数: たとえば-L 3では3個ずつ処理する
-Pだけだと必ずしも最大限(のコア数を)使ってくれるわけではない。-Lと併用して強制する。-L 1だと前のコマンドの出力結果を1個ずつ処理することになる。
このあたり詳細は
https://d.hatena.ne.jp/pasela/20120122/xargs
デバッグ
-tで実行されるコマンドを(エラー出力で)見ることができる。-Lや-Pでややこしくなった分割の様子を確認するなどデバッグに。