Умовний трубопровід


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

Зручна порада Помічена!
інвертування

6
Будьте в курсі gotchas: unix.stackexchange.com/a/39043/19055
Йонас Кёлкер

2
Повернення логіки не допомагає. Якщо catповертається з ненульовим статусом виходу (загальним при отриманні SIGPIPE), тоді cmd3буде запущено. Краще використовувати if / then / else, як у відповіді Тор або інших рішеннях, які уникають запуску cat.
Стефан Шазелас

тому кішку можна використовувати для з'єднання речей, на кшталт потоку "наскрізь"
Олександр Міллз

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-mode (як, наприклад, із сценарію, що починається з #! /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

Як доповнення до прийнятої відповіді манатурки : будьте в курсі і-false-або 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зміною все робить ваш код не має значення. Стосовно "і-false-or gotcha", я не бачу втручання в трубопровід: pastebin.com/u6Jq0bZe
манатура

5
@manatwork: одне, на що Джонас вказує, - це те, що [[ "${DEFINED}" ]] && cmd3 || cat, cmd3і catне є взаємовиключними thenта elseгалузями одного if, але те, що якщо cmd3не вдасться, то воно catтакож буде виконано. (Звичайно, якщо cmd3завжди вдається, або якщо він споживає все вхідні дані, так що не залишилося нічого в stdinпротягом catпроцесу, то це не має значення.) Якщо вам потрібна як thenі elseгілка, то краще використовувати явну ifкоманду, а НЕ &&і ||.
musiphil

1
Дякую за пояснення, @musiphil. Тепер я розумію аргумент Йонаса.
манастирство

4

У мене була подібна ситуація, яку я вирішував за допомогою функцій bash :

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:]'
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.