各行を前方に出力してから後方に出力するコマンド

各行を前方に出力してから後方に出力するコマンド

私は次のような「メタコマンド」xyzを探しています。

(echo "foo"; echo "bar") | xyz rev

次を返します。

foo oof
bar rab

一時ファイルを避けたい。つまり、次のようなよりきれいな解決策を探しています。

tempfile=$(mktemp)
cat > $tempfile
cat $tempfile | rev | paste $tempfile -

(もちろん、一般的な解決策が必要です。 だけでなく、すべてのコマンドに対して、revコマンドが各入力行に1行を出力すると仮定できます。)

Zshソリューションも許可されています。

答え1

stdioバッファリングがどのように機能するかによって、ほとんどの場合、多くの問題が発生します。 Linuxの回避策は、このプログラムを使用stdbufしてcoprocを介してコマンドを実行して、出力のインターリーブを明示的に制御することです。

以下では、コマンドが各入力行の後に1行を出力すると仮定します。

#!/bin/bash
coproc stdbuf -i0 -o0 "$@"
IFS=
while read -r in ; do
    printf "%s " "$in"
    printf "%s\n" "$in" >&${COPROC[1]}
    read -r out <&${COPROC[0]}
    printf "%s\n" "$out"
done

OPはプログラムの各行への入力のみを必要とするため、より一般的な解決策が必要な場合最後にすぐに出力せずに1行ずつ出力するには、より洗練されたアプローチが必要です。標準入力と補助プロセスからデータを読み取ろうとするイベントループを作成し、両方がread -t 0同じ「行番号」を持っている場合は出力し、それ以外の場合は行を保存します。 CPUを100%使用したくない場合は、イベントループのどのラウンドでも準備ができていない場合は、イベントループを再実行する前に少し遅れてください。プロセスがバッファリングを必要とする部分ラインを出力する場合、追加の問題が発生します。

これにもっと一般的な解決策が必要な場合は、次のようにします。予想されるすでに複数の入力ストリームでパターンマッチングを処理するための優れたサポートを提供しているからです。しかし、これはbash / zshソリューションではありません。

答え2

シェル関数。これは、<<<zshやbashを含む、この文字列をサポートするすべてのシェルで機能します。

xyz() { 
    while read line
    do 
        printf "%s " "$line"
        "$@" <<<"$line"
    done
}

$ (echo "foo"; echo "bar") | xyz rev
foo oof
bar rab

答え3

この特別な場合は、以下を使用してくださいperl

printf '%s\n' foo bar | perl -Mopen=locale -lpe '$_ .= " " . reverse$_'
foo oof
bar rab

これを拡張することで、文字列クラスタを使用できます。

$ printf '%s\n' $'complique\u301' |
    perl -Mopen=locale -lpe '$_ .= " " . join "", reverse/\X/g'
compliqué éuqilpmoc

またはzsh:

while IFS= read -r line; do
  print -r -- $line ${(j::)${(s::Oa)line}}
done

しかし、while readテキストを処理するためのループの使用を避けましょう。、でもzsh(この特別な場合でも、組み込みコマンドのみが使用されるため悪くありません)。

通常、一時ファイルを使用するのが最善の方法です。を使用すると、zsh次の操作でこれを行うことができます。

(){paste -d ' ' $1 <(rev <$1)} =(print -l foo bar)

=(...)一時ファイルの作成とクリーンアップを担当します)。

通常、teeパイプと何らかの形のingと交換すると、デッドロックが発生します。デッドロック状況に関するいくつかの方法と詳細については、次のような質問を参照してください。

答え4

ネームドパイプが救出に来ます!

xyz() {
  pipe="/tmp/$$pipe"
  mkfifo "$pipe"
  tee "$pipe" | "$@" | paste "$pipe" -
  rm "$pipe"
}

コマンド例の結果:

$ (echo "foo"; echo "bar") | xyz rev
foo oof
bar rab

上記の機能は説明したものとまったく一致しています。

関連情報