文字列を置き換えたいとき、私がつい手を伸ばすのは sed です。
でも、やりたいことが「この文字をこの文字に」だけなら、tr のほうがずっと短く書けます。
パイプの途中に挟むだけで、大文字化・記号の削除・空白の圧縮がひと息で片づくのです。
今回は tr の基本を、実際に動かした出力とセットで整理しました。
検証環境は Ubuntu 22.04.5 の GNU coreutils 8.32 です。
tr は「文字を置き換える」だけのコマンド
tr は標準入力を受け取り、指定した文字を1対1で置き換えて標準出力へ流します。
ファイル名を引数に取らないのが、最初の戸惑いポイントでしょうか。
必ずパイプかリダイレクトで流し込む、と覚えておけば迷いません。
$ echo "hello world" | tr 'a-z' 'A-Z'
HELLO WORLD
$ echo "one two three" | tr ' ' '\n'
one
two
three
第1引数が変換前の集合、第2引数が変換後の集合です。
a-z のような範囲指定も、[:lower:] のような文字クラスも、どちらも書けます。
$ echo "Hello, World! 123" | tr '[:lower:]' '[:upper:]'
HELLO, WORLD! 123
$ echo "Hello, World! 123" | tr -d '[:punct:]'
Hello World 123
記号だけをまとめて落としたいときは [:punct:] が手軽でした。
集合の長さが揃わないときの挙動
変換前と変換後で文字数が違うと、tr は黙って埋めにいきます。
$ echo "abcdef" | tr 'abcdef' 'XY'
XYYYYY
足りない分は、変換後の最後の1文字で埋められるという仕様です。
エラーにならないぶん、書き間違えても気づきにくい部分でしょう。
集合の長さは、書いた直後に数えるくせをつけておくと安全です。
-d で消す、-s で重複を潰す
置換ではなく削除したいときは -d を付けます。
$ echo "090-1234-5678" | tr -d '-'
09012345678
$ echo "a b c" | tr -s ' '
a b c
-s は squeeze の s で、連続した同じ文字を1つにまとめる指定です。
コピペした表の余分な空白を潰す用途では、これが一番出番が多いと感じています。
桁区切りやハイフンを外して数値化する前処理にも、そのまま使えますね。
-c を足すと「それ以外」が対象になる
-c は補集合の指定で、書かなかった文字のほうを処理対象にします。
-d と組み合わせると、必要な文字だけを残すホワイトリスト方式のフィルタの出来上がりです。
$ echo "Tel: 03-1234-5678 (代表)" | tr -cd '0-9\n'
0312345678
数字と改行だけを残し、あとは全部落とした結果です。
「消したい文字を列挙する」方式だと想定外の文字を取りこぼしますが、この書き方ならその心配がありません。
覚えておくと効く定番パイプ
まずは Windows 由来のファイルでよく踏む、CRLF の除去から。
$ printf 'line1\r\nline2\r\n' | tr -d '\r' | od -c | head -2
0000000 l i n e 1 \n l i n e 2 \n
0000014
od -c で確認すると、復帰コードが消えているのが目で見えます。
次は単語の出現回数を数えるパイプで、tr で改行に割ってから sort と uniq に渡す形です。
$ echo "the quick brown fox the lazy dog the fox" | tr ' ' '\n' | sort | uniq -c | sort -rn | head -3
3 the
2 fox
1 quick
sort と uniq 側の細かい話は、以前まとめた記事にゆずります。
最後は使い捨ての文字列を作るときの定番です。
$ head -c 200 /dev/urandom | tr -dc 'A-Za-z0-9' | head -c 16; echo
NdVjDUjPtQ9ISw4O
乱数が元なので、出力は当然ながら実行のたびに別物です。
ファイルを渡すときはリダイレクトで
tr の引数はあくまで文字の集合なので、ファイル名を置くとそのまま叱られます。
$ tr ',' '\t' people.csv
tr: extra operand ‘people.csv’
Try 'tr --help' for more information.
$ tr ',' '\t' < people.csv
Alice 30
Bob 25
同じ理由で、sed の -i にあたる「その場で書き換える」オプションもありません。
同じファイルへリダイレクトすると中身が消えるので、必ず別名に書き出してから差し替えます。
$ tr ',' '\t' < people.csv > people.tsv
この一手間を惜しんで空ファイルを作った経験は、たぶん誰にでもあるでしょう。
-t を知っていると事故が減る
先ほどの「最後の1文字で埋める」挙動が邪魔なときは、-t を付けます。
$ echo "abcdef" | tr 'abcdef' 'XY'
XYYYYY
$ echo "abcdef" | tr -t 'abcdef' 'XY'
XYcdef
-t は truncate の t で、変換後の集合に合わせて変換前を切り詰めます。
対応が取れている分だけ置き換えたいときは、こちらのほうが意図どおりです。
-d と -s を同時に指定できる点も、あわせて覚えておくと小回りが利きますね。
$ echo "aaa--bbb--ccc" | tr -ds '-' 'a'
abbbccc
ハイフンを削除しつつ、a の連続を1文字に潰した結果になります。
sed ではなく tr を選ぶ基準
判断はシンプルで、置き換えたい単位が「文字」なら tr、「文字列」なら sed です。
tr は正規表現を解釈しないので、メタ文字のエスケープで悩む場面がありません。
逆に「abc という並びを xyz に」という依頼は、tr には荷が重すぎます。
$ echo "aaa" | tr 'a' 'bc'
bbb
2文字目の c は無視され、a はすべて b に置き換わりました。
文字列単位の置換のつもりで書くと、この挙動で足をすくわれます。
文字列ごと差し替えたいなら、素直に sed を使いましょう。
最後に
tr は覚えることが少ないわりに、パイプの途中でよく効くコマンドです。
迷ったら -d と -s と -c の3つだけ思い出せば、たいていの用は足りるはず。
文字ではなく文字コードごと変換したい場面なら、iconv のほうが近道です。
以上です。











コメントを残す