寻找正则表达式模式,将该模式传递给脚本,并用脚本的输出替换模式

时间:2017-01-22 23:00:01

标签: bash sed scripting

每次出现模式时(在本例中为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 个答案:

答案 0 :(得分:3)

sed用于单个行上的简单替换,即全部。任何其他东西,即使它可以完成,也需要神秘的语言结构,这些结构在20世纪70年代中期被发明时已经过时,当时awk被发明并且今天纯粹用于心理锻炼。你的问题不是简单的替代,所以你不应该尝试使用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
}'

e.g。使用echo代替script.sh命令:

$ 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

答案 1 :(得分:2)

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

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

对于您的示例,我们的内容为infile

siedi87sik65owk55dkd
siedi11sik22owk33dkd

(两行演示多行的工作方式),然后是script内容

#!/bin/bash

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

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

./so pattern script [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

Command(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}"

仍然有效!

答案 2 :(得分:0)

概念上更简单的多用途解决方案

使用 GNU 实用程序:

sed

使用 BSD 实用程序(也适用于GNU实用程序):

"

我们的想法是使用$将词典上感兴趣的标记转换为包含shell命令替换的字符串,该字符串使用标记调用目标脚本,然后将结果传递给shell进行评估。

注意:

  • 输入中的所有嵌入式\xargs -d'\n'字符必须为tr '\n' '\0' - 转义。

  • xargs -0(GNU)和echo 'siedi87sik65owk55dkd' | sed 's|[0-9]\{2\}|$(./script.sh &)|g' | tr '\n' '\0' | xargs -I% sh -c 'printf "%s\n" '\"%\" / <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.yardbird.justice.yardbird.Fragments.DrinksFragment"> (BSD)只需要在输入中正确保留空格 - 如果是不需要,以下符合POSIX标准的解决方案将:

    <android.support.design.widget.AppBarLayout
    android:layout_height="wrap_content"
    android:layout_width="match_parent"
    android:theme="@style/AppTheme.AppBarOverlay">
    
    <android.support.design.widget.TabLayout
        android:id="@+id/tab_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:tabMode="fixed"
        app:tabGravity="fill"/>
    
    </android.support.design.widget.AppBarLayout>
    
    <android.support.v4.view.ViewPager
        android:id="@+id/viewPager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="47dp"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
    />