Как составить bash-функции, используя каналы?

18

У меня есть несколько функций, определенных таким образом:

function f {
  read and process $1
  ...
  echo $result
}

Я хочу собрать их вместе, чтобы вызов выглядел так f | g | h.

Какую идиому я должен использовать для преобразования функции, работающей с аргументами, в чтение аргументов из стандартного ввода? Можно ли читать пары, кортежи аргументов из потока без необходимости экранировать их (например, завершать нулем)?

Rumca
источник
Либо вы хотите что-то подобное, h(g(f(...)))либо каждая из функций читает из стандартного input ( read x; ...) и записывает в стандартный output ( echo ...).
vonbrand

Ответы:

21

Один из возможных подходов - поместить while...readвнутри ваших функций конструкцию, которая будет обрабатывать любые данные, поступившие в функцию через STDIN, работать с ней, а затем отправлять полученные данные обратно через STDOUT.

function X {
  while read data; do
    ...process...
  done
}

Необходимо уделить внимание настройке while ..read..компонентов, поскольку они будут сильно зависеть от типов данных, которые они смогут надежно использовать. Там может быть оптимальная конфигурация, которую вы можете придумать.

пример

$ logF() { while read data; do echo "[F:$(date +"%D %T")] $data"; done; }
$ logG() { while read data; do echo "G:$data";                    done; }
$ logH() { while read data; do echo "H:$data";                    done; }

Вот каждая функция сама по себе.

$ echo "hi" | logF
[F:02/07/14 20:01:11] hi

$ echo "hi" | logG
G:hi

$ echo "hi" | logH
H:hi

Вот они, когда мы используем их вместе.

$ echo "hi" | logF | logG | logH
H:G:[F:02/07/14 19:58:18] hi

$ echo -e "hi\nbye" | logF | logG | logH
H:G:[F:02/07/14 19:58:22] hi
H:G:[F:02/07/14 19:58:22] bye

Они могут принимать различные стили ввода.

#-- ex. #1
$ cat <<<"some string of nonsense" | logF | logG | logH
H:G:[F:02/07/14 20:03:47] some string of nonsense

#-- ex. #2    
$ (logF | logG | logH) <<<"Here comes another string."
H:G:[F:02/07/14 20:04:46] Here comes another string.

#-- ex. #3
$ (logF | logG | logH)
Look I can even
H:G:[F:02/07/14 20:05:19] Look I can even
type to it
H:G:[F:02/07/14 20:05:23] type to it
live
H:G:[F:02/07/14 20:05:25] live
via STDIN
H:G:[F:02/07/14 20:05:29] via STDIN
..type Ctrl + D to stop..

#-- ex. #4
$ seq 5 | logF | logG | logH
H:G:[F:02/07/14 20:07:40] 1
H:G:[F:02/07/14 20:07:40] 2
H:G:[F:02/07/14 20:07:40] 3
H:G:[F:02/07/14 20:07:40] 4
H:G:[F:02/07/14 20:07:40] 5

#-- ex. #5
$ (logF | logG | logH) < <(seq 2)
H:G:[F:02/07/14 20:15:17] 1
H:G:[F:02/07/14 20:15:17] 2
SLM
источник
4

В качестве дополнения к ответу slm я провел некоторые эксперименты с кортежами, разделенными нулями, в качестве аргументов функции:

$ sayTuple() { 
    IFS= read -r -d $'\0' d1
    IFS= read -r -d $'\0' d2
    echo "sayTuple: -$d1- -$d2-"
}

Примечания: sayTupleдважды читает запись с нулевым символом в конце, -d $'\0'обрабатывая любое пространство, окружающее ввод IFS=. echoназад записи в окружении-

Результат показывает, что он правильно обрабатывает ввод с нулем в конце, содержащий \nи \t:

$ printf "%s\0%s\0" "Hello " $' Brave\n\tWorld' | sayTuple 
sayTuple: -Hello - - Brave
        World-

Пожалуйста, добавьте предложения по улучшению в комментариях, это интересная тема.

grebneke
источник
+1 нравится твоя идея. Вместо этого мы могли бы поместить внутри цикл, который позволял бы ему принимать произвольные # аргументов. sayTuple() { arr=() ; while IFS= read -r -d $'\0' arg; do arr+="$arg"; done; echo "sayTuple: ${arr[@]}"; },
SLM
Похоже, вы должны быть в состоянии сделать, IFS= read -r -d $'\0' -a argно я не мог заставить это работать. Это позволило бы удалить while, что кажется ненужным, но было единственным шаблоном, который я мог заставить работать.
SLM