วิธีการฝังคำสั่งเชลล์ลงในนิพจน์ sed?


16

ฉันมีไฟล์ข้อความที่มีรูปแบบต่อไปนี้:

keyword value
keyword value
...

โดยที่ keyword เป็นคำเดียวและความคุ้มค่าเป็นอย่างอื่นจนกระทั่งสิ้นสุดบรรทัด ฉันต้องการอ่านไฟล์จากเชลล์สคริปต์ในวิธีที่ค่า (แต่ไม่ใช่คำหลัก) ได้รับการขยายเชลล์

ด้วยความที่เป็นเรื่องง่ายที่จะจับคู่คำหลักและชิ้นส่วนค่า

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 ควรให้คุณใช้ shell $ (คำสั่ง) หรือตัวแปร $ ภายในนิพจน์
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 เพื่อถอดรหัสสตริง hex แบบยาว . . แมว FtH.ch13 | ข้อความของ sed -r /(.* *: [) ([0-9a-fA-F] *)] / \ 1 $ (echo \ 2 | xxd -r -p)] /; s / ^ ( . *) $ / echo "\ 1" / g' | ทุบตี> FtHtext.ch13 ไหน FtH.ch13 มีเส้นเช่น "foo ทดสอบข้อความบาร์ hex: [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

คำหลักที่ได้รับอ้างถึงค่าไม่ได้ ฉันผ่านตัวเลือกe- ซึ่งเป็น GNU ที่เฉพาะเจาะจง - ไปยังsคำสั่งซึ่งบอกให้ sed ดำเนินการผลการทดแทนเป็นคำสั่งเชลล์และอ่านผลลัพธ์ในรูปแบบบัฟเฟอร์ (แม้แต่หลายบรรทัด) การใช้ตัวเลือกpafter (!) eทำให้sedการพิมพ์บัฟเฟอร์รูปแบบหลังจากที่คำสั่งถูกดำเนินการ


คุณสามารถทำได้โดยไม่ต้องทั้งใน-nและตัวเลือกคือp sed -r 's/([^ ]+) (.*)/echo "\1" \2\n/e' inputแต่ขอบคุณสำหรับสิ่งนี้! ฉันไม่ทราบเกี่ยวกับeตัวเลือก
Kaushal Modi

@KaushalModi โอ้ใช่แล้วคุณพูดถูก! ฉันกำลังนั่งอยู่บนรั้วเมื่อมันมาถึงeตัวเลือก (แนะนำโดย GNU) นั่นคือยังsed? :)
hek2mgl

มันใช้งานได้สำหรับฉัน เป็น GNU sed ตามค่าเริ่มต้นสำหรับฉัน (GNU sed เวอร์ชั่น 4.2.1) สำหรับการกระจาย RHEL
Kaushal Modi

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
ขอบคุณ แต่ฉันอยากจะหลีกเลี่ยงการใช้ภาษาสคริปต์อีกถ้าฉันสามารถและเก็บไว้กับคำสั่งยูนิกซ์มาตรฐานและบอร์นเชลล์
เออร์เนส AC

0

KISS SHORT PURE SED เท่านั้น

ฉันจะทำสิ่งนี้

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

และมันทำงานได้


คุณช่วยอธิบายวิธีการทำงานของมันได้มั้ย
elysch
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.