Условный конвейер


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
Будьте в курсе проблем: 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

В качестве дополнения к принятому ответу manatwork : осознавайте и-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» я не вижу никакого вмешательства в конвейер: pastebin.com/u6Jq0bZe
manatwork

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.