如何使用 sed 根据用户的输入通过类似堆栈的移动来移动一行?

如何使用 sed 根据用户的输入通过类似堆栈的移动来移动一行?

给定一个字符串列表:

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+1a-1前进到后行a

表示为纯粹移动一系列行,xy,向前移动到该行之后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设置xy和。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
"

相关内容