คำสั่ง build โดยการต่อสตริงเข้าใน bash


13

ฉันมีสคริปต์ทุบตีที่สร้างบรรทัดคำสั่งในสตริงตามพารามิเตอร์บางอย่างก่อนดำเนินการในครั้งเดียว ชิ้นส่วนที่ถูกต่อเข้ากับสตริงคำสั่งควรถูกคั่นด้วยไพพ์เพื่ออำนวยความสะดวกในการ "สตรีม" ของข้อมูลผ่านแต่ละส่วนประกอบ

ตัวอย่างที่ง่ายมาก:

#!/bin/bash
part1=gzip -c
part2=some_other_command
cmd="cat infile"

if [ ! "$part1" = "" ]
then
    cmd+=" | $part1"
fi


if [ ! "$part2" = "" ]
then
    cmd+=" | $part2"
fi


cmd+="> outfile"
#show command. It looks ok
echo $cmd
#run the command. fails with pipes
$cmd

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

ดังนั้นคำถามของฉันคือการสร้างคำสั่งด้วยวิธีนี้หรือไม่และวิธีที่ดีที่สุดในการทำคืออะไร


ข้อความแสดงข้อผิดพลาดคืออะไร
CameronNemo

ในสคริปต์ของฉัน (ซึ่งค่อนข้างซับซ้อนกว่าการทำให้เข้าใจง่ายนี้) ฉันได้รับ "file not found"
Lennart Rolland

มีความปลอดภัยที่จะสมมติว่าinfileมีอยู่ในไดเรกทอรีปัจจุบันหรือไม่?
saiarcot895

ใช่. ในรหัสของฉันมันเป็น wget -O - แทนไฟล์ ที่จริงแล้วถ้าฉันเพียงแค่คัดลอกสตริงที่ต่อกันและ pasete ในเทอร์มินัลมันก็ทำงานได้ดี
Lennart Rolland

คำตอบ:


17

ทุกอย่างขึ้นอยู่กับเมื่อสิ่งต่าง ๆ ได้รับการประเมิน เมื่อคุณพิมพ์$cmd, $cmdส่วนที่เหลือทั้งหมดของสายจะถูกส่งเป็นข้อโต้แย้งกับคำแรกใน

walt@spong:~(0)$ a="cat /etc/passwd"
walt@spong:~(0)$ b="| wc -l"
walt@spong:~(0)$ c="$a $b"
walt@spong:~(0)$ echo $c
cat /etc/passwd | wc -l
walt@spong:~(0)$ $c
cat: invalid option -- 'l'
Try 'cat --help' for more information.
walt@spong:~(1)$ eval $c
62
walt@spong:~(0)$ a="echo /etc/passwd"
walt@spong:~(0)$ c="$a $b"
walt@spong:~(0)$ echo $c
echo /etc/passwd | wc -l
walt@spong:~(0)$ $c
/etc/passwd | wc -l
walt@spong:~(0)$ $c |od -bc
0000000 057 145 164 143 057 160 141 163 163 167 144 040 174 040 167 143  
          /   e   t   c   /   p   a   s   s   w   d       |       w   c  
0000020 040 055 154 012  
              -   l  \n  
0000024
walt@spong:~(0)$ eval $c
1  

สิ่งนี้แสดงให้เห็นว่าข้อโต้แย้งที่ส่งผ่านไปยังechoคำสั่งคือ: " /etc/passwd", " |" (อักขระแถบแนวตั้ง), " wc" และ " -l"

จากman bash:

eval [arg ...]  
    The  args  are read and concatenated together into   
    a single command.  This command is then read and  
    executed by the shell, and its exit status is returned  
    as the value of eval.  If there are no args, or only null  
    arguments, eval returns 0.

8

ทางออกหนึ่งสำหรับการอ้างอิงในอนาคตคือการใช้ "eval" สิ่งนี้ทำให้มั่นใจได้ว่าวิธีใดก็ตามที่การตีความสตริงโดย bash นั้นถูกลืมและสิ่งทั้งหมดนั้นถูกอ่านราวกับว่ามันถูกพิมพ์ลงในเชลล์โดยตรง (ซึ่งเป็นสิ่งที่เราต้องการ)

ดังนั้นในตัวอย่างข้างต้นแทนที่

$cmd

กับ

eval $cmd

แก้ไขมัน


ระวังด้วยพารามิเตอร์ที่ยกมา จะเป็นเช่นเดียวกับeval foo "a b" eval foo "a" "b"
udondan

2

@altinator อธิบายแล้วว่าทำไมสิ่งนี้ถึงไม่ได้ผลอย่างที่คุณคาด อีกวิธีหนึ่งคือการใช้bash -cคำสั่งของคุณ:

$ comm="cat /etc/passwd"
$ comm+="| wc -l"
$ $comm
cat: invalid option -- 'l'
Try 'cat --help' for more information.
$ bash -c "$comm"
51

1
Parsimony บอกฉันว่าอย่าเริ่มกระบวนการอื่นด้วยbash -cแต่ใช้evalเพื่อทำคำสั่งในกระบวนการปัจจุบัน
waltinator

@ altinator แน่ใจว่าฉันอาจใช้ eval สำหรับเรื่องนี้ด้วย (ซึ่งเป็นเหตุผลที่ฉันสนับสนุนคุณและ Lennart) ฉันแค่ให้ทางเลือก
terdon

0

อาจเป็นวิธีที่ดีกว่าในการทำเช่นนี้คือหลีกเลี่ยงการใช้evalและเพียงใช้อาร์เรย์ Bash และเป็นการขยายแบบอินไลน์เพื่อสร้างอาร์กิวเมนต์ทั้งหมดแล้วดำเนินการกับคำสั่ง

runcmd=() # This is slightly messier than declare -a but works
for cmd in $part1 $part2 $part3; do runcmd+="| $cmd "; done
cat infile ${runcmd[@]} # You might be able to do $basecmd ${runcmd[@]}
# but that sometimes requires an `eval` which isn't great
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.