😊

シェルで途中まで入力したコマンドを一時保存して後から実行する小技

に公開18

シェルで長ったらしいコマンドを入力している途中に「先に別のコマンドを実行しておくべきだった」と気付いたことはないでしょうか。とくにワンライナースクリプトLOVEなみなさんは、こういうことがよく発生するのではないでしょうか。こういうときに泣く泣く入力中の文字列を消してから別のコマンドを実行して、いったん削除したコマンドを打ち直しているでしょうか。それとも、文字列をカットしてから後から貼り付けしているでしょうか。

こういうときに入力中だったコマンドを一時保存して後から実行する小技を紹介します。どこに一時保存するかというと、それはシェルの履歴です。具体的には「入力中だったコマンドの先頭に移動して「#」を入力してからenterを押す、他のことをした後に履歴をたどって"#"を消して実行」というものです。以下に例を示します。

  1. awk '{<長大なワンライナー>}' <input.data > output.data まで入力したところで「しまった、input.dataをまだ生成していない!」と気づく
  2. 行頭に移動して「#」を入力してenterを押す。コメントとして扱われるので何も実行されないが、履歴には(「#」付きのものが)残る。
  3. input.dataを生成するコマンドを実行する。たとえばmake input.dataなど。
  4. 「↑」などで履歴をたどる。#awk '{<長大なワンライナー>}' <input.data > output.dataが見つかる。
  5. 行頭に移動して「#」を削除してenterを押す。
  6. input.dataがあるので無事成功。

別にシェルやらターミナルやらの隠された機能を使うわけではない、知ってしまえば大したことないネタですが、身の回りに知らない人がけっこういたので、ここでも共有しました。おわり。

Discussion

-p Hk-p Hk

こんなに簡単な方法があったとは!

ilovegaloisilovegalois

Ctrl+u, Ctrl+y でも同様のことがもっと簡単にできる気がするのですが、こちらの方法を使うメリットは何でしょうか?

satsat

あいやさんがおっしゃるとおり、同様のことを複数回やったときに全て履歴に残せるのがメリットです。C-u C-yだと最新のものしか残せないです。

ilovegaloisilovegalois

なるほど、確かに仰る通りですね。2回以上Ctrl+uしたときに「あ〜さっき切り取ったやつ復元したい…」となったときの気持ちを思い出しました。

kosekikoseki

Zsh だと Ctrl-y → ESC-y → ESC-y ... で戻れるみたいですね。
Ctrl-u を yank できるのを初めて知りました。 🙏

Yoshiaki KawazuYoshiaki Kawazu

コレよくやるけど、前側のコマンドを追加修正して実行してを何度か繰り返してるうちに、# の存在を忘れたまま # の後ろのコマンド部分を直し始めてしまい、当然#の後ろを直しても何も変わらんのでうまく動作しないと悩むフェーズに入りがち…。

tomoritomori

ためになる tips を共有してくださりありがとうございます!
bash を想定されている記事かと思いますが、zsh では少し挙動が異なるので共有します。


zsh ではデフォルトで 対話モードでは # がコメントになりません
そのため、何も設定していない場合:

$ # echo hello
zsh: command not found: #

のようなエラーになります。

bash と同じく対話モードでも # 以降をコメントにしたい場合は、.zshrc に次の 1 行を追加する必要があります。

setopt INTERACTIVE_COMMENTS
TakumibooTakumiboo

エラーになっても履歴を残すという目的は達成しているのでいいような。

あいや - aiya000あいや - aiya000

へえ~、そんなオプションが…!
僕はそれを知らなかったので、:コマンドを使っていました 🙋‍♂️

$ # cp ~/Repository/vket-boilerplate-nuxt/develop/{.editorconfig,.gitattributes,.lintstagedrc.json,.prettierrc} .
zsh: command not found: #

$ : cp ~/Repository/vket-boilerplate-nuxt/develop/{.editorconfig,.gitattributes,.lintstagedrc.json,.prettierrc} .
(no output)
クロパンダクロパンダ

同じことechoでやってましたが使えるなら#のほうがベターそうですね。参考になりました

kako-junkako-jun

便利なテクをありがとうございます。

カーソルを0文字目に戻す操作を、素の状態で試してみたら
bashでは Ctrl + ←Fn + ←Alt + ← で行けました。
zshだと Ctrl + ←Fn + ← だけ行けました。

Fnのこんな機能は知りませんでした。

kako-junkako-jun

Ctrl + a でどっちのシェルも行けました。
左手だけで操作できて便利なのですね。

あと訂正で、Ctrl + a と同じ挙動は Ctrl + ← だけで
ほかは半角スペース区切りの先頭でした。

khkh

ですよね。ちなみにctrl+eで行末です。
ctrl+e ctrl+u と連続で打つと行が全て消えて便利です。

ryo.ryo.

今までは、ctrl + cで一度中断した後に、入力途中のコマンドをコピー&貼り付けしてから入力し直していたので、
とても参考になりました。

先頭に#を入れる or 先頭に#ある場合は除く、動作をキーバインドで設定すると良さそうと思ったので、zshの例ですが、zsh widgetを考えてみました。

_insert_comment() {
  if [[ $BUFFER =~ ^[[:space:]]*# ]]; then
      # 既にコメントアウトされている場合は解除
      BUFFER=${BUFFER#"${BUFFER%%[![:space:]]*}"}  # 前方の空白を削除
      BUFFER=${BUFFER#\#}  # #を削除
      BUFFER=${BUFFER#[[:space:]]}  # #の後の空白を削除
  else
      # コメントアウトされていない場合は追加
      BUFFER="# $BUFFER"
  fi
  zle redisplay
}
zle -N _insert_comment
bindkey '^Q' _insert_comment # ctrl + qで先頭に#追加 or 削除