文字列のリストが与えられたら:
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-1
a
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,b
(c
つまり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
設定する最後の内部ステートメントです。残りはちょうど健康状態を確認することです。x
y
z
答え2
ただawkを使用してください。すべてのUnixシステムのすべてのシェルでawkを使用してください。
$ awk 'NR==FNR{a[NR]=$0; next} FNR==1{tmp=a[4]; a[4]=a[2]; a[2]=tmp} {print a[FNR]}' file file
a
d
c
b
e
または、複数の行を置き換えるには、置換する行番号のペアswap[]
で次の配列を入力します。
awk '
BEGIN { split("2 4 3 5",swap) }
NR==FNR { a[NR]=$0; next }
FNR==1 {
for ( i=1; i in swap; i+=2 ) {
tmp = a[swap[i]]
a[swap[i]] = a[swap[i+1]]
a[swap[i+1]] = tmp
}
}
{ print a[FNR] }
' file file
a
d
e
b
c
答え3
### initialize variables
a=2
b=4
b1=$((b-1))
sedを使う
sed -n ":1
$a,$b{
$b1!{N;b1;}
h;n;G
}
p
" file
Perlユーティリティの使用
perl -ne "$a..$b-1"' and push(@A,$_) or print($_,splice(@A))' file
awkを使う
awk -v a="$a" -v b="$b" '
a<=NR && NR<b {x[++i]=$0;next}
{
print
for (n=i; i>0; i--)
print x[n-i+1]
}
' file
Pythonを使う:
python3 -c 'import sys, itertools as it
ors = rs = "\n"
inp = sys.argv.pop(1)
a,b = map(int,sys.argv[1:])
chomp = lambda x: x.rstrip(rs)
with open(inp) as g:
f = map(chomp,g)
for nr,_ in enumerate(f,1):
if nr < a: print(_)
else: break
G = list(it.islice(f,0,b-a))
print(G.pop(),_,*G,sep=ors)
for _ in f: print(_)
' file "$a" "$b"
GNUデスクトップ電卓ユーティリティの使用:
< file sed 's/.*/[&]/' |
dc -e "[q]se
[?pc l.1+ds. $a>p]sp
[? l.1+ds. $b>q]sq
[LMpc l*1-ds* 1<r]sr
[?z0=e pc z0=?]s?
[SM z0<w]sw
[
1s. # initialize line counter
lpx # print lines before 2
lqx # store on stack lines 2..4
?zs*p # save stack size n print nxt
lwx # save stack onto register 4..2
lrx # print register contents 2..2
l?x # print remaining lines
]s@
l@x
"