複数のスペースを一つに置換できますか(ただし引用符の中は除く)
以前 zsh で、失敗したコマンドはヒストリファイルに保存しない設定をしました(付随して、スペースで始まるコマンドは履歴に残さない、コマンド内の複数のスペースは一つに置換する、といった設定も自前で行ないました)が、使っていくうちにいくつか改善できる点が分かってきました。今回はそのうち、複数のスペースを一つに置換する設定について紹介します。
以前の設定では、複数のスペースを一つに置換する zsh のオプションである hist_reduce_blanks
相当の機能を実現するためにコマンドの文字列に対して tr -s ' '
を実行していました。大抵はこれで問題ないのですが、うまくいかない場合もあります。例えば、
grep ' something '
というコマンドは、
grep ' something '
のように履歴に保存されてほしいのですが、
grep ' something '
として保存されます。grep
と '
の間の複数のスペースは一つに置換したいけれども引用符('
や "
)の中についてはそのままにしたいわけです。
ということで、設定を変更します。なるべくシンプルなワンライナーで実現したいと思ったのですが、自分の正規表現力では無理なのでちょこちょこと検索すること数日間……。最終的に、該当個所は下記のようになりました。
local cmd_reduce_blanks if [[ ${_LASTCMD} =~ \" ]]; then cmd_reduce_blanks=$(echo ${_LASTCMD} \ | perl -pe 's/ +(?=(([^"]*"){2})*[^"]*$)/ /g') elif [[ ${_LASTCMD} =~ \' ]]; then cmd_reduce_blanks=$(echo ${_LASTCMD} \ | perl -pe "s/ +(?=(([^']*'){2})*[^']*$)/ /g") else cmd_reduce_blanks=$(echo ${_LASTCMD} | tr -s ' ') fi
この手の処理で活躍する sed や awk ではなく、Stack Overflow の回答 を参考にして Perl に頼ることになりました。"
の中以外の複数のスペースを一つに置換するコマンドは
perl -pe 's/ +(?=(([^"]*"){2})*[^"]*$)/ /g'
となります('
の中以外を対象とする場合は "
と '
を入れ替えてください)。この正規表現の具体的な説明は こちらの正規表現テスタ をご覧ください。コマンドに "
と '
の両方を使うことはまれだと思うので、どちらか一方の引用符の中かどうかを判定するロジックになっています。
上記のコードでは _LASTCMD
という変数にコマンドの文字列が保存されており(参照:以前の記事)、この文字列内の複数のスペースを一つに置換して cmd_reduce_blanks
という変数に格納しています。ちなみに "
と '
の両方を使うことはまれと書きましたが、'
を引用符としてではなくアポストロフィとして "
の引用符の中で使うことはあり得るかもしれません。例えば下記のようなコマンドです。
git commit -m "Thank God it's Friday"
このような場合でも正しく対応して
git commit -m "Thank God it's Friday"
と履歴に保存するため、if
文はコマンドが "
を含む場合を '
を含む場合よりも優先しています。いかがでしょうか。いやーしかし、正規表現、覚えられませんねー。