Làm thế nào để nhúng một lệnh shell vào một biểu thức sed?


16

Tôi có một tệp văn bản với định dạng sau:

keyword value
keyword value
...

Trong đó từ khóa là một từ duy nhất và giá trị là mọi thứ khác cho đến cuối dòng. Tôi muốn đọc tệp từ tập lệnh shell, theo cách mà các giá trị (chứ không phải từ khóa) trải qua quá trình mở rộng shell.

Với sed, thật dễ dàng để kết hợp các từ khóa và các phần giá trị

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

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

sản xuất

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

nhưng sau đó câu hỏi là làm thế nào tôi có thể nhúng một lệnh shell vào phần thay thế của biểu thức sed. Trong trường hợp này tôi muốn thay thế được \1 `echo \2`.


Uhm ... Tôi không chắc chắn đưa ra câu trả lời, nhưng sử dụng NHÂN ĐÔI được trích dẫn bằng sed nên cho phép bạn sử dụng các biến shell $ (lệnh) hoặc $ trong biểu thức.
St0rM

Câu trả lời:


18

Sed tiêu chuẩn không thể gọi shell ( GNU sed có phần mở rộng để làm điều đó , nếu bạn chỉ quan tâm đến Linux không nhúng), vì vậy bạn sẽ phải thực hiện một số xử lý bên ngoài sed. Có một số giải pháp; tất cả yêu cầu trích dẫn cẩn thận.

Không rõ chính xác bạn muốn các giá trị được mở rộng như thế nào. Ví dụ: nếu một dòng là

foo hello; echo $(true)  3

cái nào sau đây nên là đầu ra?

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

Tôi sẽ thảo luận về một số khả năng dưới đây.

vỏ nguyên chất

Bạn có thể lấy shell để đọc dòng đầu vào theo dòng và xử lý nó. Đây là giải pháp đơn giản nhất và cũng nhanh nhất cho các tệp ngắn. Đây là điều gần nhất với yêu cầu của bạn echo \2.

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

read -r keyword valueđặt $keywordthành từ được phân tách bằng khoảng trắng đầu tiên của dòng và $valuephần còn lại của dòng trừ khoảng trắng theo sau.

Nếu bạn muốn mở rộng các tham chiếu biến, nhưng không thực hiện các lệnh bên ngoài thay thế lệnh, hãy đặt $valuebên trong một tài liệu ở đây . Tôi nghi ngờ rằng đây là những gì bạn đang thực sự tìm kiếm.

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

sed đường ống vào một vỏ

Bạn có thể chuyển đổi đầu vào thành một kịch bản shell và đánh giá điều đó. Sed là nhiệm vụ, mặc dù nó không phải là dễ dàng. Thực hiện theo echo \2yêu cầu của bạn (ghi chú rằng chúng tôi cần thoát các trích dẫn đơn trong từ khóa):

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

Đi với một tài liệu ở đây, chúng ta vẫn cần phải thoát khỏi từ khóa (nhưng khác nhau).

{
  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

Đây là phương pháp nhanh nhất nếu bạn có nhiều dữ liệu: nó không bắt đầu một quy trình riêng cho mỗi dòng.

ôi

Các kỹ thuật tương tự chúng tôi đã sử dụng với sed làm việc với awk. Chương trình kết quả là dễ đọc hơn đáng kể. Đi với đỉnh điểm echo \2:

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

Sử dụng tài liệu ở đây:

awk '
  NR==1 { print "cat <<EOF" }
  1 {
      kw = $1;
      sub(/^ *[^ ]+ +/, "");
      gsub(/\\\$`/, "\\&", $1);
      print "k=<" kw "> v=<" $0 ">";
  }
  END { print "EOF" }
' | sh

câu trả lời chính xác. Tôi sẽ sử dụng giải pháp shell thuần túy, vì tệp đầu vào thực sự nhỏ và hiệu suất không phải là vấn đề đáng lo ngại, nó cũng sạch và dễ đọc.
Ernest AC

một chút hack nhưng đủ gọn gàng. ví dụ: sử dụng sed để gọi xxd để giải mã chuỗi hex dài. . . mèo FtH.ch13 | văn bản /(.* của sed -r. *: [) ([0-9a-fA-F] *)] / \ 1 $ (echo \ 2 | xxd -r -p)] /; s / ^ ( . *) $ / echo "\ 1" / g' | bash> FtHtext.ch13 đâu FtH.ch13 có dòng như "kiểm tra văn bản thanh hex foo: [666f6f0a62617200]"
gaoithe

14

Có GNU sedbạn có thể sử dụng lệnh sau:

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

Đầu ra nào:

keyword value value
keyword value  value
keyword Linux

với dữ liệu đầu vào của bạn.

Giải trình:

Lệnh sed ngăn chặn đầu ra thường xuyên bằng cách sử dụng -ntùy chọn. -rđược thông qua để sử dụng các biểu thức chính quy mở rộng giúp chúng ta tiết kiệm được một số ký tự đặc biệt trong mẫu nhưng không bắt buộc.

Các slệnh được sử dụng để chuyển dòng đầu vào cho lệnh:

echo "\1" \2

Các từ khóa được trích dẫn giá trị không. Tôi chuyển tùy chọn e- cụ thể là GNU - cho slệnh, lệnh này cho sed thực hiện kết quả thay thế dưới dạng lệnh shell và đọc kết quả của nó vào bộ đệm mẫu (Thậm chí nhiều dòng). Sử dụng tùy chọn psau (!) Làm echo việc sedin bộ đệm mẫu sau khi lệnh đã được thực thi.


Bạn có thể làm mà không cần cả hai -npcác tùy chọn tức là sed -r 's/([^ ]+) (.*)/echo "\1" \2\n/e' input. Nhưng cảm ơn vì điều này! Tôi không biết về elựa chọn này.
Kaushal Modi

@KaushalModi Ồ vâng, bạn nói đúng! Tôi đang ngồi trên hàng rào khi có etùy chọn (được giới thiệu bởi GNU). Vẫn còn đó sedchứ? :)
hek2mgl

Vâng, nó làm việc cho tôi. Đó là GNU sed theo mặc định đối với tôi (GNU sed phiên bản 4.2.1) trên bản phân phối RHEL.
Kaushal Modi

4

Bạn có thể thử phương pháp này:

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')"

(nếu tôi có ý định của bạn ngay). Đó là chèn "process" vào đầu mỗi dòng không trống để biến nó thành tập lệnh như:

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

được đánh giá ( eval) trong đó process là một hàm in thông điệp mong đợi.


1

Nếu một giải pháp không phải là sed được chấp nhận, đoạn PERL này sẽ thực hiện công việc:

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

1
cảm ơn nhưng tôi muốn tránh sử dụng một ngôn ngữ kịch bản khác nếu tôi có thể và giữ nó theo các lệnh unix tiêu chuẩn và bourne shell
Ernest AC

0

CHỈ KISS SHORT PURE SED

tôi sẽ làm việc này

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

và nó hoạt động.


Bạn có thể vui lòng giải thích làm thế nào nó hoạt động?
elysch
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.