ไปป์ไลน์แบบมีเงื่อนไข


39

ว่าฉันมีท่อต่อไปนี้:

cmd1 < input.txt |\
cmd2 |\
cmd4 |\
cmd5 |\
cmd6 |\
(...) |\
cmdN > result.txt

ภายใต้เงื่อนไขบางผมอยากจะเพิ่มcmd3ระหว่างและcmd2 cmd4มีวิธีในการสร้างไปป์ไลน์แบบมีเงื่อนไขโดยไม่บันทึกผลลัพธ์ของ cmd2 ลงในไฟล์ชั่วคราวหรือไม่? ฉันจะคิดถึงสิ่งที่ชอบ:

cmd1 < input.txt |\
cmd2 |\
(${DEFINED}? cmd3 : cat ) |\
cmd4 |\
cmd5 |\
cmd6 |\
(...) |\
cmdN > result.txt

คำตอบ:


36

เพียงแค่ปกติ&&และ||ผู้ประกอบการ:

cmd1 < input.txt |
cmd2 |
( [[ "${DEFINED}" ]] && cmd3 || cat ) |
cmd4 |
cmd5 |
cmd6 |
(...) |
cmdN > result.txt

(โปรดทราบว่าไม่จำเป็นต้องมีเครื่องหมายแบ็กสแลชต่อท้ายเมื่อบรรทัดลงท้ายด้วยไพพ์)

อัพเดทตามสังเกตโจนัส
หาก cmd3 อาจยุติด้วยรหัสออกที่ไม่ใช่ศูนย์และคุณไม่ต้องการcatประมวลผลอินพุตที่เหลือให้ย้อนกลับตรรกะ:

cmd1 < input.txt |
cmd2 |
( [[ ! "${DEFINED}" ]] && cat || cmd3 ) |
cmd4 |
cmd5 |
cmd6 |
(...) |
cmdN > result.txt

เคล็ดลับที่มีประโยชน์ตั้งข้อสังเกต!
กลับ


2
การย้อนกลับตรรกะไม่ได้ช่วย หากcatกลับมาพร้อมกับสถานะออกที่ไม่เป็นศูนย์ (ทั่วไปเมื่อรับ SIGPIPE) จากนั้นcmd3จะถูกเรียกใช้ ใช้ดีกว่าหาก / แล้ว / อื่นเช่นเดียวกับในคำตอบของ ธ อร์หรือการแก้ปัญหาอื่น ๆ catที่หลีกเลี่ยงการทำงาน
Stéphane Chazelas

ดังนั้นแมวสามารถใช้เพื่อเชื่อมต่อสิ่งต่าง ๆ ได้เช่นกระแส "ผ่าน"
Alexander Mills

19

if/ else/ fiงาน สมมติว่าเชลล์คล้ายบอร์นใด ๆ :

cmd1 < input.txt |
cmd2 |
if [ -n "$DEFINED" ]; then cmd3; else cat; fi |
cmd4 |
cmd5 |
cmd6 |
(...) |
cmdN > result.txt

10

คำตอบทั้งหมดที่ได้รับเพื่อให้ห่างไกลแทนที่ด้วยcmd3 catคุณสามารถหลีกเลี่ยงการรันคำสั่งใด ๆ ด้วย:

if [ -n "$DEFINE" ]; then
  alias maybe_cmd3='cmd3 |'
else
  alias maybe_cmd3=''
fi
cmd1 |
cmd2 |
maybe_cmd3
cmd4 |
... |
cmdN > result.txt

นั่นคือ POSIX แต่โปรดทราบว่าหากในbashสคริปต์ที่bashไม่ได้อยู่ในshโหมด (เช่นเริ่มต้นด้วยสคริปต์#! /path/to/bash) คุณจะต้องเปิดใช้งานการขยายนามแฝงด้วยshopt -s expand_aliases(หรือset -o posix)

วิธีการอื่นที่ยังคงไม่เรียกใช้คำสั่งที่ไม่จำเป็นคือการใช้ eval:

if [ -n "$DEFINE" ]; then
  maybe_cmd3='cmd3 |'
else
  maybe_cmd3=''
fi
eval "
  cmd1 |
  cmd2 |
  $maybe_cmd3
  cmd4 |
  ... |
  cmdN > result.txt"

หรือ:

eval "
  cmd1 |
  cmd2 |
  ${DEFINE:+cmd3 |}
  cmd4 |
  ... |
  cmdN > result.txt"

บน Linux (อย่างน้อย) แทนคุณcatสามารถใช้pv -qที่ใช้splice()แทนread()+ write()เพื่อส่งผ่านข้อมูลระหว่างสองไพพ์ซึ่งหลีกเลี่ยงการย้ายข้อมูลสองครั้งระหว่างเคอร์เนลและพื้นที่ผู้ใช้


9

ในฐานะที่เป็นภาคผนวกของคำตอบที่ได้รับการยอมรับของ manatwork : ระวังเรื่อง and-false-or gotcha ตลอดจนปฏิสัมพันธ์กับลำธาร ตัวอย่างเช่น

true && false || echo foo

fooเอาท์พุท ไม่น่าแปลกใจ

true && (echo foo | grep bar) || echo baz

และ

echo foo | (true && grep bar || echo baz)

bazทั้งการส่งออก (โปรดทราบว่าecho foo | grep barเป็นเท็จและไม่มีเอาต์พุต) อย่างไรก็ตาม

echo foo | (true && grep bar || sed -e abaz)

ไม่มีผลอะไร นี่อาจเป็นหรือไม่ใช่สิ่งที่คุณต้องการ


ฉันไม่เห็นว่าสิ่งนี้เกี่ยวข้องกันอย่างไร else(หรือ||) ควรทำหน้าที่เป็นโมฆะการดำเนินการรักษาการป้อนข้อมูลที่ไม่เปลี่ยนแปลง ดังนั้นการแทนที่catด้วยechoการเปลี่ยนแปลงทุกอย่างที่ทำให้รหัสของคุณไม่เกี่ยวข้อง เกี่ยวกับ“ and-false-or gotcha” ฉันไม่เห็นสิ่งรบกวนกับ
ไพพ์

5
@manatwork: สิ่งหนึ่งที่โจนาสจะชี้ให้เห็นคือว่าใน[[ "${DEFINED}" ]] && cmd3 || cat, cmd3และcatไม่ได้เป็นพิเศษร่วมกันthenและelseสาขาเดียวกันifแต่ถ้าcmd3ล้มเหลวแล้วcatยังจะต้องถูกประหารชีวิต (แน่นอนถ้าcmd3เสมอประสบความสำเร็จหรือถ้ามันกินทุกการป้อนข้อมูลเพื่อให้มีอะไรเหลืออยู่ในstdinสำหรับการcatที่จะดำเนินการแล้วมันอาจจะไม่เป็นเรื่อง.) หากคุณจำเป็นต้องใช้ทั้งthenและelseสาขามันจะดีกว่าที่จะใช้อย่างชัดเจนifคำสั่งไม่ได้และ&& ||
musiphil

1
ขอบคุณสำหรับคำอธิบาย @musiphil ตอนนี้ฉันเข้าใจข้อโต้แย้งของโจนัสแล้ว
จัดการ

4

ฉันมีสถานการณ์คล้ายกันที่ฉันแก้ไขด้วยฟังก์ชันทุบตี:

if ...; then
  my_cmd3() { cmd3; }
else
  my_cmd3() { cat; }
if

cmd1 < input.txt |
cmd2 |
my_cmd3 |
cmd4 |
cmd5 |
cmd6 |
(...) |
cmdN > result.txt

3

นี่คือทางเลือกอื่นที่เป็นไปได้:
เชื่อมต่อไพพ์ที่มีชื่อเพื่อสร้างไพพ์ไลน์หลอก:

dir="$(mktemp -d)"
fifo_n='0'
function addfifo {
 stdin="$dir/$fifo_n"
((fifo_n++))
[ "$1" ] || mkfifo "$dir/$fifo_n"
stdout="$dir/$fifo_n"; }

addfifo; echo "The slow pink fox crawl under the brilliant dog" >"$stdout" &

# Restoring the famous line: "The quick brown fox jumps over the lazy dog"
# just replace 'true' with 'false' in any of the 5 following lines and the pseudo-pipeline will still work
true && { addfifo; sed 's/brilliant/lazy/' <"$stdin" >"$stdout" & }
true && { addfifo; sed 's/under/over/'     <"$stdin" >"$stdout" & }
true && { addfifo; sed 's/crawl/jumps/'    <"$stdin" >"$stdout" & }
true && { addfifo; sed 's/pink/brown/'     <"$stdin" >"$stdout" & }
true && { addfifo; sed 's/slow/quick/'     <"$stdin" >"$stdout" & }

addfifo -last; cat <"$stdin"

wait; rm -r "$dir"; exit 0

2

สำหรับการอ้างอิงในอนาคตหากคุณมาที่นี่เพื่อค้นหาท่อที่มีเงื่อนไขในปลานี่คือวิธีที่คุณทำ:

set -l flags "yes"
# ... 
echo "conditionalpipeline" | \
  if contains -- "yes" $flags 
    sed "s/lp/l p/"
  else
    cat
  end | tr '[:lower:]' '[:upper:]'
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.