2017-01-22 67 views
1

每当模式显示出来(在本例中为2位数的情况),我想通过模式转换为脚本,并用脚本的输出替换该模式。寻找正则表达式,将该模式传递给脚本,并用脚本的输出替换模式

我使用的sed的它应该是什么样子的例子是

echo 'siedi87sik65owk55dkd' | sed 's/[0-9][0-9]/.\/script.sh/g' 

眼下这个返回

siedi./script.shsik./script.showk./script.shdkd 

但我想它返回

siedi!!!87!!!sik!!!65!!!owk!!!55!!!dkd 

这就是./script.sh中的内容

#!/bin/bash 

echo "!!!$1!!!" 

它必须被替换为输出。在这个例子中,我知道我可以使用正常的sed替换,但我不希望这是一个答案。

回答

3

sed用于简单替换个别行,即全部。除此之外,即使可以这样做,也需要奥术语言结构,这些语言结构在20世纪70年代中期发明并且今天纯粹用于心理练习时已经过时了。你的问题不是一个简单的替换,所以你不应该尝试使用sed来解决它。

你会想是这样的:

awk '{ 
    head = "" 
    tail = $0 
    while (match(tail,/[0-9]{2}/)) { 
     tgt = substr(tail,RSTART,RLENGTH) 
     cmd = "./script.sh " tgt 
     if ((cmd | getline line) > 0) { 
      tgt = line 
     } 
     close(cmd) 
     head = head substr(tail,1,RSTART-1) tgt 
     tail = substr(tail,RSTART+RLENGTH) 
    } 
    print head tail 
}' 

例如到位的script.sh命令的使用echo

$ echo 'siedi87sik65owk55dkd' | 
awk '{ 
    head = "" 
    tail = $0 
    while (match(tail,/[0-9]{2}/)) { 
     tgt = substr(tail,RSTART,RLENGTH) 
     cmd = "echo !!!" tgt "!!!" 
     if ((cmd | getline line) > 0) { 
      tgt = line 
     } 
     close(cmd) 
     head = head substr(tail,1,RSTART-1) tgt 
     tail = substr(tail,RSTART+RLENGTH) 
    } 
    print head tail 
}' 
siedi!!!87!!!sik!!!65!!!owk!!!55!!!dkd 
+0

getline之后是什么?你为什么设置tgt = line。 另外为什么你必须关闭(cmd) – Jacob

+0

我用'cmd |的结果替换'tgt'的原始值。 getline'只有在'cmd | getline'成功了,否则我会离开'tgt'它的原始值。有关如何/何时使用getline(来自管道)的详细信息,请参阅https://www.gnu.org/software/gawk/manual/gawk.html#Getline_002fPipe和http://awk.freeshell.org/AllAboutGetline。 –

2

Ed的awk solution显然是走在这里的方式。

为了好玩,我试着想出一个sed解决方案,这里是(一个错综复杂的GNU sed),它将模式和脚本作为参数运行;输入可以从标准输入读取(也就是说,您可以管道输入)或从作为第三个参数提供的文件中读取。

对于你的榜样,我们不得不infile与内容

siedi87sik65owk55dkd 
siedi11sik22owk33dkd 

(两行说明它如何工作的多线),然后用script内容

#!/bin/bash 

echo "!!!${1}!!!" 

,最终解决方案脚本本身,so。用法是

./so patternscript [input]

其中pattern是一个扩展的正则表达式的GNU的sed(与-r选项)所理解的那样,script是要为每场比赛运行命令的名称,以及可选的input是输入文件的名称,如果输入不是标准输入。

对于你的榜样,这将是

./so '[[:digit:]]{2}' script infile 

,或者作为过滤器,

cat infile | ./so '[[:digit:]]{2}' script 

与输出

siedi!!!87!!!sik!!!65!!!owk!!!55!!!dkd 
siedi!!!11!!!sik!!!22!!!owk!!!33!!!dkd 

这是so样子:

#!/bin/bash 

pat=$1      # The pattern to match 
script=$2     # The command to run for each pattern 
infile=${3:-/dev/stdin}  # Read from standard input if not supplied 

# Use sed and have $pattern and $script expand to the supplied parameters 
sed -r " 
    :build_loop      # Label to loop back to 
    h         # Copy pattern space to hold space 
    s/.*($pat).*/.\/\"$script\" \1/ # (1) Extract last match and prepare command 
    # Replace pattern space with output of command 
    e 
    G         # (2) Append hold space to pattern space 
    s/(.*)$pat(.*)/\1~~~\2/   # (3) Replace last match of pattern with ~~~ 
    /\n[^\n]*$pat[^\n]*$/b build_loop # Loop if string contains match 
    :fill_loop       # Label for second loop 
    s/(.*\n)(.*)\n([^\n]*)~~~([^\n]*)$/\1\3\2\4/ # (4) Replace last ~~~ 
    t fill_loop      # Loop if there was a replacement 
    s/(.*)\n(.*)~~~(.*)$/\2\1\3/  # (5) Final ~~~ replacement 
" < "$infile" 

sed命令适用于两个循环。第一个将模式空间复制到保留空间,然后从模式空间中除去最后一个匹配的所有内容,并准备要运行的命令。与置换后(1)在其意见中,图案空间看起来像这样:

./script 55 

e命令(GNU扩展),则替换该命令的输出的模式空间。在此之后,G将保留空间附加到模式空间(2)。该模式空间现在看起来是这样的:

!!!55!!! 
siedi87sik65owk55dkd 

在(3)取代了最后一场比赛用细绳希望不等于模式,我们得到

!!!55!!! 
siedi87sik65owk~~~dkd 

的循环重复,如果最后取代模式空间的线条仍然与模式匹配。经过三个环路,模式空间看起来是这样的:

!!!87!!! 
!!!65!!! 
!!!55!!! 
siedi~~~sik~~~owk~~~dkd 

第二个循环,现在替换最后~~~与第二与替代(4)模式空间的最后一道防线。该命令使用大量的“不是换行符”([^\n])来确保我们不会错误地替换~~~

因为(4)写入方式命令,循环使用最后一个替代结束去,所以命令(5)之前,我们有这个模式空间:

!!!87!!! 
siedi~~~sik!!!65!!!owk!!!55!!!dkd 

命令(5)一个简单版本的命令(4),并且在它之后,输出是按照需要的。

这似乎是相当强大,可以处理在脚本的名称空间,只要调用时,它的正确引用来运行:

./so '[[:digit:]]{2}' 'my script' infile 

这将失败,如果

  • 输入文件包含~~~(可通过替换开始处的所有事件,将它们放回末尾来解决)
  • script的输出包含~~~
  • 图案包含~~~

即,该解决方案非常依赖于~~~是唯一的。


因为没有人问:so作为一个班轮。

#!/bin/bash 
sed -re ":b;h;s/.*($1).*/.\/\"$2\" \1/;e" -e "G;s/(.*)$1(.*)/\1~~~\2/;/\n[^\n]*$1[^\n]*$/bb;:f;s/(.*\n)(.*)\n([^\n]*)~~~([^\n]*)$/\1\3\2\4/;tf;s/(.*)\n(.*)~~~(.*)$/\2\1\3/" < "${3:-/dev/stdin}" 

仍然有效!

+1

埃德称这是一个“心理练习” - 这是非常准确的。 (虽然心理练习很有趣!) –

+1

它的美妙之处在于没有人能够理解它来告诉你它是否有任何错误:-)。它可能会在三月的第二个星期天发射天网,我们永远不会知道。直到3月...... :-)。 –

+1

@EdMorton我希望有人对此进行严格的测试;) –

0

概念比较简单的多效用溶液

使用GNU实用程序:

echo 'siedi87sik65owk55dkd' | 
    sed 's|[0-9]\{2\}|$(./script.sh &)|g' | 
    xargs -d'\n' -I% sh -c 'echo '\"%\" 

使用BSD公用事业(还与GNU工具):

echo 'siedi87sik65owk55dkd' | 
    sed 's|[0-9]\{2\}|$(./script.sh &)|g' | tr '\n' '\0' | 
    xargs -0 -I% sh -c 'echo '\"%\" 

The的想法是使用sed将感兴趣的令牌词法化地转换为包含shell命令替换的字符串,该替换使用令牌调用目标脚本,然后将结果传递给shell进行评估。

注:

  • 任何嵌入式"$输入字符必须是\转义。

  • xargs -d'\n'(GNU)和tr '\n' '\0'/xargs -0(BSD)时,才需要在输入正确地保留空白 - 如果这是不需要的,下面的POSIX兼容的解决方案将做:

    echo 'siedi87sik65owk55dkd' | 
        sed 's|[0-9]\{2\}|$(./script.sh &)|g' | tr '\n' '\0' | 
        xargs -I% sh -c 'printf "%s\n" '\"%\"