开发者问题收集

一个有关 shell 脚本的 sed 和管道的简单问题

2023-05-12
142

需要保存一个 sed 和脚本问题

我想编写一个脚本并执行类似这样的操作 整个任务是编写一个 shell 脚本并将参数转换为 sed 使用的命令,然后调用 sed 从 stdin 打印指定的行。

seq 1 100 | ./script.sh 1 9 34-35

并使用以下命令获取答案:

1 9 34 35

下面是我的代码

#!/bin/bash

str="sed -n -e '"

data=$(cat)
#echo "$data"

for arg in $@;do
 if [ `expr index $arg -` -eq 0 ]; then
   str="$str${arg}p;"
 else
   len=`expr length $arg`
   i=`expr index $arg -`
   i=`expr $i - 1`
   tmp1=`expr substr $arg 1 $i`
   str="$str${tmp1},"
   i=`expr $i + 2`
   tmp2=`expr substr $arg $i $len`
   str="$str${tmp2}p;"
 fi
done
str="$str'"
echo "$str"
`echo $data | $str`
#echo `$str`
~                

我收到错误 sed:-e 表达式 #1,字符 1:未知命令:`''

问题是什么 以及 我该如何正确执行此操作?

2个回答

您的代码构建了一个字符串。

使用参数 1 9 34-35 ,您将获得: sed -n -e '1p;9p;34,35p;'

您调用:

`echo $data | $str`

这变成了两个通过管道连接在一起的命令:

  • echo 使用参数: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100

  • sed 带参数: -n -e '1p;9p;34,35p;'

请注意,shell 不会对单词进行进一步的操作。特别是,引号剥离已经发生(扩展之前没有)。

因此 sed 接收一个以 ' 开头的脚本参数。

' 不是有效的 sed 命令,因此会打印错误。

请注意, $data 应该被引用,否则 shell 将剥离换行符(如上所示),并且运行时 echo 将仅输出一行。

还请注意,如果 sed 成功,反引号将导致 shell 尝试将其输出作为新命令运行。这似乎不太可能是您想要的。

要修复您的代码,通过分离命令和参数来避免混淆会更有意义:

#!/bin/bash

str=

data=$(cat)
#echo "$data"

for arg in $@;do
 if [ `expr index $arg -` -eq 0 ]; then
   str="$str${arg}p;"
 else
   len=`expr length $arg`
   i=`expr index $arg -`
   i=`expr $i - 1`
   tmp1=`expr substr $arg 1 $i`
   str="$str${tmp1},"
   i=`expr $i + 2`
   tmp2=`expr substr $arg $i $len`
   str="$str${tmp2}p;"
 fi
done
echo `echo "$data" | sed -n -e "$str"`

您可以简化/现代化为:

#!/bin/bash

str=

for arg; do
 if [ $(expr index $arg -) -eq 0 ]; then
   str="$str${arg}p;"
 else
   len=$(expr length $arg)
   i=$(expr index $arg -)
   i=$(expr $i - 1)
   tmp1=$(expr substr $arg 1 $i)
   str="$str${tmp1},"
   i=$(expr $i + 2)
   tmp2=$(expr substr $arg $i $len)
   str="$str${tmp2}p;"
 fi
done

# this command will already read from stdin, so no need to buffer the data
echo $(sed -n -e "$str")

expr 使用起来相当繁琐。 利用 bash 的内置功能,您可以进一步简化:

#!/bin/bash

str=

for arg; do
    str=$str${arg/-/,}"p;"
done

echo $(sed -n "$str") # output is combined into single line
                      # for separate lines, just do: sed -n "$str"

或者使用参数验证:

#!/bin/bash

str=

for arg; do
    if ! [[ $arg =~ ^[0-9]+(-[0-9]+)?$ ]]; then
        echo bad args 1>&2
        exit 1
    fi
    str=$str${arg/-/,}"p;"
done

echo $(sed -n "$str")

这些版本中的任何一个都会产生问题的示例输出。

jhnc
2023-05-12

这可能对您有用(GNU bash 和 sed):

f(){ <<<"$@" sed 'y/-/,/;s/\S\+/&=\n/g'; }
seq 100 | sed -nf <(f 1 9 34-35) | paste -sd' '

创建一个函数 f ,解析所需的参数并生成 sed 脚本以输出输入文件所需的行号。

通过管道传输文件(或提供文件名)并使用 sed 调用,该调用从进程替换中获取 sed 命令并终止隐式打印以将这些行号输出到另一个管道,该管道按顺序粘贴结果并用空格分隔。

该命令可以作为 cat 命令或输入文件调用。

 cat file | sed -nf <(f 1 9 34-35) | paste -sd' '

或:

sed -nf <(f 1 9 34-35) file | paste -sd' '

该过程可以变成一行:

sed -nf <(<<<'1 9 34-45' sed 'y/-/,/;s/\S\+/&=;/g') file | paste -sd' '
potong
2023-05-13