Как встроить команду оболочки в выражение sed?

16

У меня есть текстовый файл в следующем формате:

keyword value
keyword value
...

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

С помощью sed легко сопоставлять ключевые слова и значимые части

input='
keyword value value
keyword "value  value"
keyword `uname`
'

echo "$input"|sed -e 's/^\([^[:space:]]*\)[[:space:]]\(.*\)$/k=<\1> v=<\2>/'

который производит

k=<keyword> v=<value value>
k=<keyword> v=<"value  value">
k=<keyword> v=<`uname`>

но тогда возникает вопрос, как я могу встроить команду оболочки в заменяющую часть выражения sed. В этом случае я бы хотел, чтобы замена была \1 `echo \2`.

Эрнест AC
источник
Хм ... Я не уверен, что приведу это как ответ, но использование DOUBLE, заключенного в кавычки с sed, позволит вам использовать shell $ (command) или $ variable внутри выражения.
St0rM

Ответы:

18

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

Непонятно, как именно вы хотите, чтобы значения были расширены. Например, если строка

foo hello; echo $(true)  3

какой из следующих выводов должен быть?

k=<foo> value=<hello; echo   3>
k=<foo> value=<hello; echo   3>
k=<foo> value=<hello; echo 3>
k=<foo> value=<foo hello
  3>

Я расскажу о нескольких возможностях ниже.

чистая оболочка

Вы можете заставить оболочку читать строку ввода построчно и обрабатывать ее. Это самое простое решение, а также самое быстрое для коротких файлов. Это самое близкое к вашему требованию « echo \2»:

while read -r keyword value; do
  echo "k=<$keyword> v=<$(eval echo "$value")>"
done

read -r keyword valueзадает $keywordпервое разделенное пробелами слово строки, а $valueостальной части строки минус конечный пробел.

Если вы хотите расширить ссылки на переменные, но не выполнять команды вне подстановок команд, поместите их $valueв документ здесь . Я подозреваю, что это то, что вы действительно искали.

while read -r keyword value; do
  echo "k=<$keyword> v=<$(cat <<EOF
$value
EOF
)>"
done

седь по трубам

Вы можете преобразовать ввод в сценарий оболочки и оценить его. Сед справляется с задачей, хотя это не так просто. Следуя вашему echo \2требованию « » (обратите внимание, что нам нужно избегать одинарных кавычек в ключевом слове):

sed  -e 's/^ *//' -e 'h' \
     -e 's/[^ ]*  *//' -e 'x' \
     -e 's/ .*//' -e "s/'/'\\\\''/g" -e "s/^/echo 'k=</" \
     -e 'G' -e "s/\n/>' v=\\</" -e 's/$/\\>/' | sh

Переходя к документу здесь, нам все еще нужно экранировать ключевое слово (но по-другому).

{
  echo 'cat <<EOF'
  sed -e 's/^ */k=</' -e 'h' \
      -e 's/[^ ]*  *//' -e 'x' -e 's/ .*//' -e 's/[\$`]/\\&/g' \
      -e 'G' -e "s/\n/> v=</" -e 's/$/>/'
  echo 'EOF'
 } | sh

Это самый быстрый метод, если у вас много данных: он не запускает отдельный процесс для каждой строки.

AWK

Те же самые методы, которые мы использовали с sed, работают с awk. Полученная программа значительно более читабельна. Идем с « echo \2»:

awk '
  1 {
      kw = $1;
      sub(/^ *[^ ]+ +/, "");
      gsub(/\047/, "\047\\\047\047", $1);
      print "echo \047k=<" kw ">\047 v=\\<" $0 "\\>";
  }' | sh

Используя здесь документ:

awk '
  NR==1 { print "cat <<EOF" }
  1 {
      kw = $1;
      sub(/^ *[^ ]+ +/, "");
      gsub(/\\\$`/, "\\&", $1);
      print "k=<" kw "> v=<" $0 ">";
  }
  END { print "EOF" }
' | sh
Жиль "ТАК - прекрати быть злым"
источник
отличный ответ. Я собираюсь использовать чистое решение для оболочки, так как входной файл действительно мал, а производительность не имеет значения, а также он чистый и читаемый.
Эрнест А.С.
немного взломать, но достаточно аккуратно. например, используйте sed для вызова xxd для декодирования длинной шестнадцатеричной строки. , , кот FtH.ch13 | sed -r 's /(.* text. *: [) ([0-9a-fA-F] *)] / \ 1 $ (echo \ 2 | xxd -r -p)] /; s / ^ ( . *) $ / echo "\ 1" / g '| bash> FtHtext.ch13 Где в FtH.ch13 есть такие строки, как "шестнадцатеричный текстовый тест foo bar: [666f6f0a62617200]"
gaoithe
14

Имея GNU, sedвы можете использовать следующую команду:

sed -nr 's/([^ ]+) (.*)/echo "\1" \2\n/ep' input

Какие выводы:

keyword value value
keyword value  value
keyword Linux

с вашими входными данными.

Объяснение:

Команда sed подавляет обычный вывод с помощью -nопции.-rпередается для использования расширенных регулярных выражений, что спасает нас от некоторых специальных символов в шаблоне, но это не требуется.

Команда sиспользуется для передачи строки ввода в команду:

echo "\1" \2

Ключевое слово get указывает значение not. Я передаю опцию e- специфичную для GNU - sкоманде, которая говорит sed выполнять результат подстановки как команду оболочки и считывать ее результаты в буфер шаблонов (даже в несколько строк). Использование опции pafter (!) eПриводит к sedпечати буфера шаблонов после выполнения команды.

hek2mgl
источник
Можно обойтись и без как -nи pварианты , то есть sed -r 's/([^ ]+) (.*)/echo "\1" \2\n/e' input. Но спасибо за это! Я не знал об этом eварианте.
Каушал Моди
@KaushalModi Ах, да, ты прав! Я сижу на заборе, когда дело доходит до eварианта (введенного GNU). Это все еще sed? :)
hek2mgl
Ну, это сработало для меня. Это GNU sed по умолчанию для меня (GNU sed версия 4.2.1) в дистрибутиве RHEL.
Каушал Моди
4

Вы можете попробовать этот подход:

input='
keyword value value
keyword "value  value"
keyword `uname`
'

process() {
  k=$1; shift; v="$*"
  printf '%s\n' "k=<$k> v=<$v>"
}

eval "$(printf '%s\n' "$input" | sed -n 's/./process &/p')"

(если я правильно понял ваше намерение). То есть вставьте «process» в начале каждой непустой строки, чтобы сделать его похожим на скрипт:

process keyword value value
process keyword "value  value"
process keyword `uname`

быть оцененным ( eval) где процесс - это функция, которая печатает ожидаемое сообщение.

Стефан Шазелас
источник
1

Если приемлемо решение, не относящееся к sed, этот фрагмент PERL сделает эту работу:

$ echo "$input" | perl -ne 'chomp; /^\s*(.+?)\s+(.+)$/ && do { $v=`echo "$2"`; chomp($v); print "k=<$1> v=<$v>\n"}'
Тердон
источник
1
спасибо, но я бы предпочел не использовать другой язык сценариев, если смогу, и придерживаюсь стандартных команд Unix и оболочки Bourne
Ernest AC
0

ТОЛЬКО ЧИСТЫЙ ЧИСТЫЙ САД

я сделаю это

echo "ls_me" | sed -e "s/\(ls\)_me/\1/e" -e "s/to be/continued/g;"

и это работает.

mr.tee
источник
Не могли бы вы объяснить, как это работает?
elysch