Pipa bersyarat

39

Katakanlah saya sudah mendapat saluran pipa berikut:

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

Dalam kondisi tertentu saya ingin menambahkan cmd3antara cmd2dan cmd4. Apakah ada cara untuk membuat semacam pipa kondisional tanpa menyimpan hasil cmd2 ke dalam file sementara? Saya akan memikirkan sesuatu seperti:

cmd1 < input.txt |\
cmd2 |\
(${DEFINED}? cmd3 : cat ) |\
cmd4 |\
cmd5 |\
cmd6 |\
(...) |\
cmdN > result.txt
Pierre
sumber
terkait: stackoverflow.com/questions/2520085/…
Ciro Santilli 新疆 改造 中心 法轮功 六四 事件

Jawaban:

36

Seperti biasa &&dan ||operator:

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

(Perhatikan bahwa tidak ada garis miring terbalik yang diperlukan saat garis berakhir dengan pipa.)

Perbarui menurut pengamatan Jonas .
Jika cmd3 dapat diakhiri dengan kode keluar bukan nol dan Anda tidak ingin catmemproses input yang tersisa, balikkan logika:

cmd1 < input.txt |
cmd2 |
( [[ ! "${DEFINED}" ]] && cat || cmd3 ) |
cmd4 |
cmd5 |
cmd6 |
(...) |
cmdN > result.txt
manatwork
sumber
Tip berguna Tercatat
balikkan
6
Waspadalah terhadap gotcha
Jonas Kölker
2
Membalik logika tidak membantu. Jika catkembali dengan status keluar non-nol (umum ketika mendapatkan SIGPIPE), maka cmd3akan dijalankan. Lebih baik gunakan if / then / else seperti pada jawaban Thor atau solusi lain yang tidak berjalan cat.
Stéphane Chazelas
jadi kucing dapat digunakan untuk menghubungkan hal-hal, seperti aliran "melalui"
Alexander Mills
19

if/ else/ fibekerja. Dengan asumsi shell seperti Bourne:

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

Semua jawaban yang diberikan sejauh ini diganti cmd3dengan cat. Anda juga dapat menghindari menjalankan perintah apa pun dengan:

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

Itu POSIX, tetapi perhatikan bahwa jika dalam bashskrip bashyang tidak dalam sh-mode (seperti skrip yang dimulai dengan #! /path/to/bash), Anda harus mengaktifkan alias ekspansi dengan shopt -s expand_aliases(atau set -o posix).

Pendekatan lain yang masih tidak menjalankan perintah yang tidak perlu adalah menggunakan eval:

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

Atau:

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

Di Linux (setidaknya), alih-alih cat, Anda bisa menggunakan pv -qyang menggunakan splice()alih-alih read()+ write()untuk melewatkan data di antara dua pipa yang menghindari data dipindahkan dua kali antara ruang kernel dan pengguna.

Stéphane Chazelas
sumber
9

Sebagai tambahan untuk jawaban manatwork yang diterima : waspadai dan-false-atau gotcha dan interaksinya dengan stream. Contohnya,

true && false || echo foo

output foo. Tidak mengherankan,

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

dan

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

keduanya output baz. (Catatan yang echo foo | grep barsalah dan tidak memiliki output). Namun,

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

tidak menghasilkan apa-apa. Ini mungkin atau mungkin bukan yang Anda inginkan.

Jonas Kölker
sumber
Saya tidak bisa melihat bagaimana ini relevan di sini. The else(atau ||) harus bertindak sebagai operasi nol menjaga input tidak berubah. Jadi, ganti catdengan echoperubahan, semuanya membuat kode Anda tidak relevan. Mengenai "dan-false-or gotcha", saya tidak melihat adanya gangguan dengan pipeline: pastebin.com/u6Jq0bZe
manatwork
5
@manatwork: Satu hal yang Jonas tunjukkan adalah bahwa, di [[ "${DEFINED}" ]] && cmd3 || cat, cmd3dan catbukan merupakan cabang yang sama-sama eksklusif thendan elsesama if, tetapi jika cmd3gagal, maka catjuga akan dieksekusi. (Tentu saja, jika cmd3selalu berhasil, atau jika mengkonsumsi semua input sehingga tidak ada yang tersisa stdinuntuk catdiproses, maka itu mungkin tidak masalah.) Jika Anda membutuhkan keduanya thendan elsecabang, lebih baik menggunakan ifperintah eksplisit , bukan &&dan ||.
musiphil
1
Terima kasih atas penjelasannya, @musiphil. Sekarang saya mengerti argumen Jonas.
manatwork
4

Saya memiliki situasi serupa yang saya selesaikan dengan fungsi bash :

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

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

Berikut ini adalah alternatif solusi yang mungkin:
menggabungkan pipa bernama untuk membuat pseudo-pipeline:

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
Claudio
sumber
2

Untuk referensi di masa mendatang, jika Anda datang ke sini mencari pipa bersyarat pada ikan , ini adalah bagaimana Anda melakukannya:

set -l flags "yes"
# ... 
echo "conditionalpipeline" | \
  if contains -- "yes" $flags 
    sed "s/lp/l p/"
  else
    cat
  end | tr '[:lower:]' '[:upper:]'
Jorge Bucaran
sumber