Як вбудувати команду shell у вираз 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`.


Ум ... я не так впевнений, щоб дати це як відповідь, але використання DOUBLE, цитируемого sed, повинно дозволяти вам використовувати оболонку $ (команда) або $ змінні всередині виразу.
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

sed труби в оболонку

Ви можете перетворити вхідний текст у сценарій оболонки та оцінити це. Sed вирішує завдання, хоча це не так просто. Відповідаючи вашій 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

чудова відповідь. Я буду використовувати рішення чистої оболонки, оскільки вхідний файл дійсно невеликий, а продуктивність не викликає особливих проблем, також чиста і читабельна.
Ернест AC

трохи зламати, але досить акуратно. наприклад, використовуйте sed для виклику xxd для декодування довгих шістнадцяткових рядків. . . кішка FtH.ch13 | sed -r 's /(.* текст. *: [) ([0-9a-fA-F] *)] / \ 1 $ (відлуння \ 2 | xxd -r -p)] /; s / ^ ( . *) $ / echo "\ 1" / g '| bash> FtHtext.ch13 Де FtH.ch13 має рядки типу "foo bar hex text test: [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 цитується значення не. Я передаю варіант e- який є специфічним для GNU - до sкоманди, яка говорить sed виконувати результат заміни як команду оболонки і читати її результати в буфер шаблону (Навіть кілька рядків). Використання опції pпісля (!) eРобить sedдрук буфера шаблону після виконання команди.


Можна обійтися і без як -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 keyword value value
process keyword "value  value"
process keyword `uname`

підлягає оцінці ( eval), коли процес - це функція, яка друкує очікуване повідомлення.


1

Якщо рішення, що не стосується седу, прийнятне, цей фрагмент PERL зробить роботу:

$ echo "$input" | perl -ne 'chomp; /^\s*(.+?)\s+(.+)$/ && do { $v=`echo "$2"`; chomp($v); print "k=<$1> v=<$v>\n"}'

1
дякую, але я б краще уникати використання іншої мови скриптів, якщо я можу, і дотримуватися її стандартних команд Unix та обробляти оболонку
Ernest AC

0

ТІЛЬКИ ПОПУСК КОРОТКИЙ ЧИСТИЙ СІД

Я це зроблю

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

і це працює.


Чи можете ви поясніть, як це працює?
elysch
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.