grepとpecoによる順次絞り込みするシェルスクリプト


名付けてgrepeco

peco/peco: Simplistic interactive filtering tool

色んなデータを柔軟に絞り込みしたい

各種のデータをパイプで処理するとき、適当にワードで絞り込んでからpecoでインクリメンタルサーチしたいことがよくある。これを簡単にできるようにしたい。

想定される用途はこんな感じ。

$ ls | grepeco hoge

パイプで与えられたデータを、ターミナルから入力された文字列で絞り込む。

パイプからの入力とターミナルからの入力を区別する

ここで、パイプで与えられたデータとはシェルスクリプトの標準入力、ターミナルから入力された文字列とは引数である。

シェルスクリプト内において、この二つは以下のように得ることができる。

# 標準入力を得る
cat -
# 引数を得る
echo "$1" # 1つめ
echo "$*" # すべて(1つの文字列として)
for i in "$@" # すべて(配列として)
do
  echo "arg: $i"
done

この場合、引数を配列として扱うのであれば、複数の検索語による、複数の検索結果からさらに絞り込むことができる。

コード

以上を踏まえて、コードは以下のようになる。

grepeco.sh
#!/bin/bash
# 引数を単一の文字列として扱う
# 変数を初期化
pipes=()
prompt="select stdin from pipe"
optm=""
usage () {
  echo "Usage: grepeco [-1h] [-p prompt] <args>" 1>&2
  echo "  -1: find only one" 1>&2
  echo "  -p: change peco prompt" 1>&2
  echo "  -h: view help" 1>&2
}
while getopts "1p:h" OPT
do
  case $OPT in
    1)
      optm="m1" # grepのオプションを変更
      ;;
    p)
      prompt="$OPTARG" # pecoのプロンプトを変更
      ;;
    h)
      usage
      exit 0
      ;;
    ?)
      echo "ERROR: Undifined option" 1>&2
      usage
      exit 1
      ;;
  esac
done
shift $(($OPTIND - 1))
if [ -p /dev/stdin ];then # パイプ入力があるか判断
  while read pipe
  do
    pipes+=("$pipe")
  done < <(if [ -n "$1" ]; then # 引数があるか判断
    cat - | grep -i"$optm" "$*"
  else
    cat -
  fi)
else
  echo "ERROR: No stdin from pipe" 1>&2
  exit 1
fi
case  "${#pipes[@]}" in # pipesの要素数による分岐
  0 )
    echo "ERROR: No hit." 1>&2
    exit 1
    ;;
  1)
    echo "${pipes[*]}"
    ;;
  *)
    for i in "${pipes[@]}"
    do
      echo "$i"
    done | peco --prompt "$prompt"
    ;;
esac
grepeco.sh
#!/bin/bash
# 引数を配列として扱う
-  done < <(if [ -n "$1" ]; then
-    cat - | grep -i"$optm" "$*"
-  else
-    cat -
-  fi)

+  done < <(if [ -n "$1" ]; then
+    stdin="$(cat -)" # ループで標準出力がリセットされるため変数に格納
+    for arg in "$@"
+    do
+      echo "$stdin" | grep -i "$arg"
+    done | sort -u # 重複する結果を削除
+  else
+    cat -
+  fi)

if文の結果をwhile文に渡すということを思いついてコードがシンプルになった。

参考