2017-01-22 5 views
1

Для каждого раза, когда отображается шаблон (в этом примере - случай с 2-значным числом), я хочу передать это шаблон сценарию и заменить этот шаблон на выход скрипта.Поиск шаблона регулярного выражения, передача этого шаблона скрипту и замена шаблона на выход скрипта

Я использую СЭД примера того, что он должен выглядеть как бы

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 для простых подстановок на отдельных линиях, то есть все. Все остальное, даже если это может быть сделано, требует тайных языковых конструкций, которые стали устаревшими в середине 1970-х годов, когда 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 
}' 

например используя 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 
+0

Что такое линия после getline? Почему вы устанавливаете tgt = line. Также почему вы должны закрыть (cmd) – Jacob

+0

Я заменяю исходное значение 'tgt' на результат' cmd | getline', только если 'cmd | getline' преуспел, иначе я оставляю 'tgt' с его исходным значением. См. Https://www.gnu.org/software/gawk/manual/gawk.html#Getline_002fPipe и http://awk.freeshell.org/AllAboutGetline для получения подробной информации о том, как и когда использовать getline (из канала). –

2

Эд awk solution, очевидно, путь здесь.

Для удовольствия я попытался придумать решение sed, и вот (свернутый GNU sed), который заставляет шаблон и сценарий запускаться как параметры; вход считывается из стандартного ввода (т. е. вы можете подключиться к нему) или из файла, предоставленного в качестве третьего аргумента.

Для примера, мы должны были бы infile с содержанием

siedi87sik65owk55dkd 
siedi11sik22owk33dkd 

(две строки, чтобы продемонстрировать, как это работает для нескольких линий), затем script с содержанием

#!/bin/bash 

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

и, наконец, решение сам скрипт, so. Использование является

./so patternscript [input]

где pattern является расширенное регулярное выражение в понимании GNU СЭД (с опцией -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}" 

еще работы!

+1

Эд называет это «умственным упражнением», что очень точно. (Разумные упражнения - это весело!) –

+1

И красоту этого никто не может понять, чтобы рассказать вам, есть ли в нем ошибки :-). Это может запустить skynet во второе воскресенье марта, и мы никогда не узнаем. До марта конечно .... :-). –

+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 '\"%\" 

идея состоит в том, чтобы использовать sed для перевода лексем лексически в строку, содержащую замены команд оболочки, которые вызывают целевой скрипт с помощью токена, а затем передают результат оболочке для оценки.

Примечание:

  • Все встроенные " и $ символов на входе должен быть \ убежал.

  • 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" '\"%\"