给定一个字符串列表:
a
b
c
d
e
用户输入:将第 4 行移至第 2 行。
所以,输出将是:
a
d
b
c
e
如何才能实现这一目标?最好使用sed
'shold 和模式空间命令。
答案1
将第 4 行向后移动到第 1 行之后与移动 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、3 和 4 行移动到第 5 行之后),请使用3,4
用作第二个表达式的范围,并5
代替4
最后一个表达式的范围。
一般来说,将行移动a
到后行b
(其中移动朝向文件开头)可以表示为将行移动b+1
到a-1
前进到后行a
。
表示为纯粹移动一系列行,x
到y
,向前移动到该行之后z
,我们可以编写一个sed
命令来向前移动一系列行,如下所示:
sed -e "$x { h; d; }" -e "$((x+1)),$y { H; d; }" -e "$z G" file
例如,“将第 2 行和第 3 行移到第 4 行之后”(这就是问题所问的内容):
$ 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
例如,“将第一行和第二行移到末尾”
$ 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
请注意,我们不能使用它来移动单身的向前行,因为第二个表达式中的范围将被反转并生成错误的输出。对于单行范围,必须绕过第二个表达式,或者我们可以用来$x,$y { H; $x h; d; }
替换第一个和第二个表达式(这H
对我们需要的范围内的所有其他行执行不必要的一行测试和不必要的行号测试)移动,但简化了后面脚本中的表达和逻辑)。
在 shell 脚本中,给定两个行号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
,即“将第 4 行移至第 1 行之后”:
$ 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
上面 shell 脚本的概括,将范围移动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 机器上的任何 shell 中使用任何 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
"