sedを使用してスタックなどの移動を介してユーザー入力に基づいて行を移動する方法は?

sedを使用してスタックなどの移動を介してユーザー入力に基づいて行を移動する方法は?

文字列のリストが与えられたら:

a
b
c
d
e

ユーザー入力:行4を行2に移動します。

したがって、出力は次のようになります。

a
d
b
c
e

これはどのように達成できますか?sed'sholdコマンドとパターンスペースコマンドを使用する方が良いでしょう。

ベストアンサー1

1行以降4行を後ろに移動するのは、2行、3行を移動するのと同じです。今後4行目以降。

$ sed -e '2 { h; d; }' -e '3 { H; d; }' -e '4 G' file
a
d
b
c
e

つまり、行2を予約済みスペースに保存します。次に、他のすべての行H(つまり、行3のみ)のスペースを予約するために追加の項目を移動する必要があります。範囲を移動したい行に予約済みのスペースを追加しますG

5行を2行後方に移動するには(つまり、2行、3行、4行を5行後方に移動する)、2番目の式3,4として使用された範囲を使用し、最後の式の範囲を5置き換えます。4

通常、1行をa次の行に移動することb(ファイルの先頭に向かって移動する場合)は、1行を次の行にb+1移動することで表現できます。a-1a

x単に一連の行をに移動すると表現すると、その行にy前に移動した後、次のように一連の行を前に移動するコマンドをz作成できます。sed

sed -e "$x { h; d; }" -e "$((x+1)),$y { H; d; }" -e "$z G" file

たとえば、「行 4 の後に行 2 と 3 を移動します」(質問での質問です):

$ x=2 y=3 z=4; sed -e "$x { h; d; }" -e "$((x+1)),$y { H; d; }" -e "$z G" file
a
d
b
c
e

たとえば、「最初の行と2番目の行を最後に移動します。」

$ x=1 y=2 z='$'; sed -e "$x { h; d; }" -e "$((x+1)),$y { H; d; }" -e "$z G" file
c
d
e
a
b

これを使って移動することはできません。一つ2番目の式の範囲が反転して誤った出力が生成されるため、今後の手順に進みます。単一行範囲の場合は、2番目の式を無視する必要があります。それ以外の場合は$x,$y { H; $x h; d; }、最初と2番目の式をH移動しましたが(不要な1行テストと不要な行番号テストを実行しますが、後のスクリプトでは式と論理を簡素化します)、置き換えることができます。

シェルスクリプトで2つの行番号a(移動したい行)と、b後に行を挿入したい行が与えられたら、a次のことができます。

if [ "$a" -eq "$b" ] || [ "$a" -eq "$((b+1))" ]; then
    # No move
    set -- -e b
else

    if [ "$a" -lt "$b" ]; then
        # Move forwards
        x="$a" y="$a" z="$b"
    else
        # Move backwards
        x="$((b+1))" y="$((a-1))" z="$a"
    fi

    set -- -e "$x,$y { H; $x h; d; }" -e "$z G"
fi

sed "$@" file

テスト:

$ a=4 b=1 sh script
a
d
b
c
e
$ a=1 b=4 sh script
b
c
d
a
e

edエディタを使用すると、次のようにしてこれを行うことができます4m1。 「ライン1の後にライン4を移動する」:

$ printf '%s\n' 4m1 ,p Q | ed -s file
a
d
b
c
e

「行 1 を行 4 に戻る」:

$ printf '%s\n' 1m4 ,p Q | ed -s file
b
c
d
a
e

上記のシェルスクリプトの要約によると、範囲を行末a,bcつまりa,b m cの内容ed)に移動するには、次のようにしますsed

# Moves range a,b to after line c
# Corresponds to "a,b m c" in ed(1)

if [ "$a" -gt "$b" ]; then
    # Error
    echo 'Range is reversed' >&2
    exit 1
elif [ "$c" -ge "$a" ] && [ "$c" -lt "$b" ]; then
    # Error
    echo 'Can not move range to within range' >&2
    exit 1
fi

if [ "$b" -eq "$c" ] || [ "$a" -eq "$((c+1))" ]; then
    # No move
    set -- -e b
else

    if [ "$a" -lt "$c" ]; then
        # Move forwards
        x="$a" y="$b" z="$c"
    else
        # Move backwards
        x="$((c+1))" y="$((a-1))" z="$b"
    fi

    set -- -e "$x,$y { H; $x h; d; }" -e "$z G"

fi

sed "$@" file

この答えで詳しく扱う他の部分と同様に、スクリプトの必須部分は移動方向に基づいて、およびをif設定する最後の内部ステートメントです。残りはちょうど健康状態を確認することです。xyz

おすすめ記事