👀

lookコマンドにタブ文字を渡したい

2022/11/16に公開

lookコマンドとその1ユースケース

lookコマンドは、ソート済みのテキストファイルから、指定した文字列で始まる行を素早く見つけ出すコマンドです。このコマンドは、ソート済みのテキストファイルを対象としていることからもわかる通り、メモリに読み込んだテキストを二分探査することを基本に成立しています。

このlookを用いて、タブ区切りテキストからテキストの一部を抽出することを考えます。仮にタブ区切りテキストの第1カラム(先頭のカラム)にユーザーIDを置き、それ以降のカラムにユーザーに関連する情報を置いておくことで、RDBMSやKey-Valueストアなどのデータベースの簡易な代替品として利用できるというわけです。

さて、タブ区切りテキストから検索する際の課題として、検索文字列にタブ文字を含める必要があります。タブ文字を含めない場合、look「指定した文字列で始まる行」を抽出するので、意図しない行を抽出してしまう場合があるのです。99を検索した場合に99は見つかりますが、同時に9919999なども同時に引っかかってしまう可能性があります。

$ look 99 sorted.txt
99      foo
991     bar
9999    baz

これを避けるために99{タブ文字}のように、タブ文字を含めて検索する必要があるわけです。

タブ文字を含めることの難しさ

気軽に「タブ文字を含める」と書きましたが、UNIX的なシェルを使う上でこの「タブ文字を含める」のには、ちょっとしたハマりポイントが幾つもあります。本記事の残りでは、それらのハマりポイントと回避方法を紹介していきます。

タブ文字は入力が困難

まずそもそもの話として、タブ文字を入力するのが普通は困難です。インタラクティブシェルであれば、多くの場合タブ文字は補完のトリガーキーとなっており、入力できません。これはだいたいはCtrl-Vに続けてTabキーを押すことで、補完のトリガーを避けて入力できます。この場合Ctrl-Vには「次のキーを解釈せずにそのまま入力する」機能を期待しています。またこのような機能は、Vimをはじめ多くのエディタにも実装されているので、シェルスクリプトにタブ文字を含める際にも利用できます。

タブ文字は区切り文字である

次の問題は、タブ文字は空白文字であるため、インタラクティブシェルの区切り文字として認識されるという点です。それを避けるためにはタブ文字をクォートで囲う必要があります。下記の例はabcfooの間にタブ文字があるのですが、片方ではタブ文字が無視されているのに対し、クォートで囲った方はタブ文字が維持されていることを示しています。

$ echo abc      foo
abc foo

$ echo "abc     foo"
abc     foo

タブ文字は不可視

最後の大きな問題は、タブ文字が不可視であるという点です。前述のecho "abc foo"を見ても、そこにタブ文字があるということはわかりません。ここでプログラミングをかじったことがある人は\tというエスケープが使えるかも、と考えるでしょう。でもそれはうまくいきません。シェルのダブルクォートは\tというエスケープを解釈しないのです。

$ echo "abc\tfoo"
abc\tfoo

$ echo "abc\\tfoo"
abc\tfoo

解決策

ではどうするのかというと、printfなどのタブ文字を出力できる、別のコマンドを利用するのです。またprintfの出力をそのまま引数に渡してしまうと、区切り文字として解釈されてしまうので、それを避けるために適宜パイプで渡すかクォートする必要があります。

$ printf "abc\tfoo" | xargs -d '\n' echo
abc     foo

$ echo "$(printf "abc\tfoo")"
abc     foo

$ echo "`printf "abc\tfoo"`"
abc     foo

以上のことからlook99{タブ文字}を検索するには、次のようにする必要があるとわかります。

$ look "$(printf "99\t")" sorted.txt
99      foo

$ look "`printf "99\t"`" sorted.txt
99      foo

これによりタブ文字を入力しづらい問題と、タブ文字が不可視であるためわかりにくい問題が、同時に解決できます。

追記

たぶんbash専用の別回答

https://twitter.com/kyoh86/status/1592726307254964224

Discussion