мишень + кошка: используйте вывод несколько раз и затем свяжите результаты

Я не знаю, возможно ли это, но я обычно вставляю, со всеми версиями VI, строка с числами, которые могут помочь мне определить местоположение столбцов.

В командном режиме:

10a1234567890

Я получу строку 100 символов (просто изменяют число повторения для меньше или больше).

18
04.03.2013, 21:01
3 ответа

Вы могли использовать комбинацию GNU stdbuf и pee от moreutils:

echo "Hello world!" | stdbuf -o 1M pee cmd1 cmd2 cmd3 > output

моча popen(3)s те 3 командных строки оболочки и затем freads вход и fwrites все это три, который будет буферизован к до 1M.

Идея состоит в том, чтобы иметь буфер, по крайней мере, столь же большой как вход. Этот путь даже при том, что три команды запускаются одновременно, они будут только видеть, что вход входит когда pee pcloses три команды последовательно.

На каждого pclose, pee сбрасывает буфер к команде и ожидает ее завершения. Это гарантирует что настолько же долго как они cmdx команды не начинают производить что-либо, прежде чем они получили любой вход (и не разветвляйте процесс, который может продолжить производить после того, как их родитель возвратился), вывод трех команд не будет чередован.

В действительности это немного похоже на использование временного файла в памяти с недостатком, что 3 команды запускаются одновременно.

Чтобы постараться не запускать команды одновременно, Вы могли записать pee как функция оболочки:

pee() (
  input=$(cat; echo .)
  for i do
    printf %s "${input%.}" | eval "$i"
  done
)
echo "Hello world!" | pee cmd1 cmd2 cmd3 > out

Но остерегайтесь, который окружает кроме zsh перестал бы работать для двоичного входа с символами NUL.

Это избегает использования временных файлов, но это означает, что целый вход хранится в памяти.

В любом случае необходимо будет сохранить вход где-нибудь в памяти или временном файле.

На самом деле это - вполне интересный вопрос, поскольку это показывает нам, предел идеи Unix наличия нескольких простых инструментов сотрудничает к единственной задаче.

Здесь, мы хотели бы иметь несколько инструментов, сотрудничают к задаче:

  • исходная команда (здесь echo)
  • команда диспетчера (tee)
  • некоторые команды фильтрации (cmd1, cmd2, cmd3)
  • и команда агрегирования (cat).

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

В случае одной команды фильтрации это легко:

src | tee | cmd1 | cat

Все команды выполняются одновременно, cmd1 начинает громко жевать данные из src как только это доступно.

Теперь, с тремя командами фильтрации, мы можем все еще сделать то же: запустите их одновременно и соедините их с каналами:

               ┏━━━┓▁▁▁▁▁▁▁▁▁▁┏━━━━┓▁▁▁▁▁▁▁▁▁▁┏━━━┓
               ┃   ┃░░░░2░░░░░┃cmd1┃░░░░░5░░░░┃   ┃
               ┃   ┃▔▔▔▔▔▔▔▔▔▔┗━━━━┛▔▔▔▔▔▔▔▔▔▔┃   ┃
┏━━━┓▁▁▁▁▁▁▁▁▁▁┃   ┃▁▁▁▁▁▁▁▁▁▁┏━━━━┓▁▁▁▁▁▁▁▁▁▁┃   ┃▁▁▁▁▁▁▁▁▁┏━━━┓
┃src┃░░░░1░░░░░┃tee┃░░░░3░░░░░┃cmd2┃░░░░░6░░░░┃cat┃░░░░░░░░░┃out┃
┗━━━┛▔▔▔▔▔▔▔▔▔▔┃   ┃▔▔▔▔▔▔▔▔▔▔┗━━━━┛▔▔▔▔▔▔▔▔▔▔┃   ┃▔▔▔▔▔▔▔▔▔┗━━━┛
               ┃   ┃▁▁▁▁▁▁▁▁▁▁┏━━━━┓▁▁▁▁▁▁▁▁▁▁┃   ┃
               ┃   ┃░░░░4░░░░░┃cmd3┃░░░░░7░░░░┃   ┃
               ┗━━━┛▔▔▔▔▔▔▔▔▔▔┗━━━━┛▔▔▔▔▔▔▔▔▔▔┗━━━┛

Который мы можем сделать относительно легко с именованными каналами:

pee() (
  mkfifo tee-cmd1 tee-cmd2 tee-cmd3 cmd1-cat cmd2-cat cmd3-cat
  { tee tee-cmd1 tee-cmd2 tee-cmd3 > /dev/null <&3 3<&- & } 3<&0
  eval "$1 < tee-cmd1 1<> cmd1-cat &"
  eval "$2 < tee-cmd2 1<> cmd2-cat &"
  eval "$3 < tee-cmd3 1<> cmd3-cat &"
  exec cat cmd1-cat cmd2-cat cmd3-cat
)
echo abc | pee 'tr a A' 'tr b B' 'tr c C'

(выше } 3<&0 должен работать вокруг факта это & перенаправления stdin от /dev/null, и мы используем <> избегать открытия каналов для блокирования до другого конца (cat) открылся также),

Или избегать именованных каналов, немного более мучительно с zsh coproc:

pee() (
  n=0 ci= co= is=() os=()
  for cmd do
    eval "coproc $cmd $ci $co"

    exec {i}<&p {o}>&p
    is+=($i) os+=($o)
    eval i$n=$i o$n=$o
    ci+=" {i$n}<&-" co+=" {o$n}>&-"
    ((n++))
  done
  coproc :
  read -p
  eval tee /dev/fd/$^os $ci "> /dev/null &" exec cat /dev/fd/$^is $co
)
echo abc | pee 'tr a A' 'tr b B' 'tr c C'

Теперь, вопрос: после того как все программы запущены и соединены, будет поток данных?

У нас есть два ограничения:

  • tee подача все его выводы на том же уровне, таким образом, это может только диспетчеризировать данные по курсу своего самого медленного выходного канала.
  • cat только начнет читать из второго канала (передайте по каналу 6 в рисунке выше), когда все данные были считаны с первого (5).

То, что это означает, - то, что данные не будут течь в канале 6 до cmd1 закончился. И, как в случае tr b B выше, который может означать, что данные не будут течь в канале 3 или, что означает, это не будет течь ни в одном из каналов 2, 3 или 4 с тех пор tee подача на самом медленном уровне всех 3.

На практике те каналы имеют непустой размер, таким образом, некоторым данным удастся пройти, и в моей системе, по крайней мере, я могу заставить это работать до:

yes abc | head -c $((2 * 65536 + 8192)) | pee 'tr a A' 'tr b B' 'tr c C' | uniq -c -c

Кроме того, с

yes abc | head -c $((2 * 65536 + 8192 + 1)) | pee 'tr a A' 'tr b B' 'tr c C' | uniq -c

У нас есть мертвая блокировка, где мы находимся в этой ситуации:

               ┏━━━┓▁▁▁▁2▁▁▁▁▁┏━━━━┓▁▁▁▁▁5▁▁▁▁┏━━━┓
               ┃   ┃░░░░░░░░░░┃cmd1┃░░░░░░░░░░┃   ┃
               ┃   ┃▔▔▔▔▔▔▔▔▔▔┗━━━━┛▔▔▔▔▔▔▔▔▔▔┃   ┃
┏━━━┓▁▁▁▁1▁▁▁▁▁┃   ┃▁▁▁▁3▁▁▁▁▁┏━━━━┓▁▁▁▁▁6▁▁▁▁┃   ┃▁▁▁▁▁▁▁▁▁┏━━━┓
┃src┃██████████┃tee┃██████████┃cmd2┃██████████┃cat┃░░░░░░░░░┃out┃
┗━━━┛▔▔▔▔▔▔▔▔▔▔┃   ┃▔▔▔▔▔▔▔▔▔▔┗━━━━┛▔▔▔▔▔▔▔▔▔▔┃   ┃▔▔▔▔▔▔▔▔▔┗━━━┛
               ┃   ┃▁▁▁▁4▁▁▁▁▁┏━━━━┓▁▁▁▁▁7▁▁▁▁┃   ┃
               ┃   ┃██████████┃cmd3┃██████████┃   ┃
               ┗━━━┛▔▔▔▔▔▔▔▔▔▔┗━━━━┛▔▔▔▔▔▔▔▔▔▔┗━━━┛

Мы заполнили каналы 3 и 6 (64 кибибита каждый). tee считал, что дополнительный байт, это подало его к cmd1, но

  • это теперь заблокировало запись на канале 3, поскольку это ожидает cmd2 освободить его
  • cmd2 не может освободить его, потому что это заблокировало запись на канале 6, ожидая cat освободить его
  • cat не может освободить его, потому что это ожидает, до там больше не вводится на канале 5.
  • cmd1 не может сказать cat там больше не вводится, потому что это ожидает само более входа от tee.
  • и tee не может сказать cmd1 там больше не вводится, потому что это заблокировалось... и так далее.

У нас есть цикл зависимостей и таким образом мертвая блокировка.

Теперь, каково решение? Большие каналы 3 и 4 (достаточно большой для содержания всего из srcвывод), сделал бы это. Мы могли сделать это, например, путем вставки pv -qB 1G между tee и cmd2/3 где pv мог сохранить до 1G данных, ожидающих cmd2 и cmd3 считать их. Это означало бы две вещи хотя:

  1. это использует потенциально большую память, и кроме того, копируя его
  2. этому не удается иметь все 3 команды, сотрудничают потому что cmd2 в действительности только начал бы обрабатывать данные, когда cmd1 закончился.

Решение второй проблемы состояло бы в том, чтобы сделать каналы 6 и 7 больших также. Принятие этого cmd2 и cmd3 произведите так очень вывод, как они используют, который не использовал бы больше памяти.

Единственный способ постараться не копировать данные (в первой проблеме) состоял бы в том, чтобы реализовать хранение данных в диспетчере самих, который является реализацией вариация на tee это может подать данные по курсу самого быстрого вывода (содержащий данные для питания более медленных в их собственном темпе). Едва ли тривиальный.

Так, в конце лучшее, которое мы можем обоснованно получить без программирования, является, вероятно, чем-то как (синтаксис Zsh):

max_hold=1G
pee() (
  n=0 ci= co= is=() os=()
  for cmd do
    if ((n)); then
      eval "coproc pv -qB $max_hold $ci $co | $cmd $ci $co | pv -qB $max_hold $ci $co"
    else
      eval "coproc $cmd $ci $co"
    fi

    exec {i}<&p {o}>&p
    is+=($i) os+=($o)
    eval i$n=$i o$n=$o
    ci+=" {i$n}<&-" co+=" {o$n}>&-"
    ((n++))
  done
  coproc :
  read -p
  eval tee /dev/fd/$^os $ci "> /dev/null &" exec cat /dev/fd/$^is $co
)
yes abc | head -n 1000000 | pee 'tr a A' 'tr b B' 'tr c C' | uniq -c
27
27.01.2020, 19:46
  • 1
    Вы правы, мертвая блокировка является самой большой проблемой, которую я нашел до сих пор для избегания использования временных файлов. Эти файлы, кажется, довольно быстры, тем не менее, я не знаю, кэшируются ли они где-нибудь, я боялся времен доступа к диску, но они кажутся разумными до сих пор. строка –  Trylks 11.03.2013, 19:36
  • 2
    Дополнительное +1 поскольку хорошее ASCII-творчество :-) –  Kurt Pfeifle 16.08.2015, 18:00

То, что Вы предлагаете, не может быть сделано легко ни с какой существующей командой и не имеет большого смысла так или иначе. Вся эта мысль о каналах (| в Unix/Linux), это в cmd1 | cmd2 cmd1 вывод записей (самое большее) до буфера памяти заполняется, и затем cmd2 выполнения, считывающие данные с буфера (самое большее), пока это не пусто. Т.е. cmd1 и cmd2 выполненный одновременно, никогда не необходимо иметь больше, чем ограниченный объем данных "в полете" между ними. Если Вы хотите подключить несколько исходных данных к единственному выводу, если один из читателей отстает от других любой, который Вы останавливаете другие (какой смысл того, чтобы работать параллельно затем?) или Вы прячете вывод, который отстающий еще не считал (какой смысл того, чтобы не иметь промежуточного файла затем?). Плюс целая синхронизация становится намного более сложным.

За мои почти 30 лет на опыте Unix я не помню ситуации, которая действительно принесла бы пользу для такого нескольким - выходной канал.

Можно ли объединить несколько выводов в один поток сегодня, просто не каким-либо чередованным способом (как должен выводы cmd1 и cmd2 быть чередованными? одна строка в свою очередь? сменяться пишущий 10 байтов? чередовать "абзацы", определенные так или иначе? и если Вы просто ничего не пишете в течение долгого времени? все это сложно для обработки). Это сделано, например. (cmd1; cmd2; cmd3) | cmd4, программы cmd1,cmd2, и cmd3 выполняются один за другим, вывод отправляется, как введено в cmd4.

3
27.01.2020, 19:46

Для Вашей перекрывающейся проблемы, на Linux (и с bash или zsh но не с ksh93), Вы могли сделать это как:

somefunction()
(
  if [ "$1" -eq 1 ]
  then
    echo "Hello world!"
  else
    exec 3> auxfile
    rm -f auxfile
    somefunction "$(($1 - 1))" >&3 auxfile 3>&-
    exec cat <(command1 < /dev/fd/3) \
             <(command2 < /dev/fd/3) \
             <(command3 < /dev/fd/3)
  fi
)

Отметьте использование (...) вместо {...} получить новый процесс при каждом повторении, таким образом, у нас может быть новый fd 3, указывающий на новое auxfile. < /dev/fd/3 прием должен получить доступ к этому теперь удаленный файл. Это не будет работать над системами кроме Linux где < /dev/fd/3 похож dup2(3, 0) и так fd 0 было бы открыто в режиме только для записи с курсором в конце файла.

Для предотвращения ветвления для вложенного somefunction Вы могли записать это как:

somefunction()
{
  if [ "$1" -eq 1 ]
  then
    echo "Hello world!"
  else
    {
      rm -f auxfile
      somefunction "$(($1 - 1))" >&3 auxfile 3>&-
      exec cat <(command1 < /dev/fd/3) \
               <(command2 < /dev/fd/3) \
               <(command3 < /dev/fd/3)
    } 3> auxfile
  fi
}

Оболочка заботилась бы о резервном копировании fd 3 при каждом повторении. Вы закончили бы тем, что закончились дескрипторы файлов раньше все же.

Хотя Вы найдете, что это - более эффективное, чтобы сделать это как:

somefunction() {
  if [ "$1" -eq 1 ]; then
    echo "Hello world!" > auxfile
  else
    somefunction "$(($1 - 1))"
    { rm -f auxfile
      cat <(command1 < /dev/fd/3) \
          <(command2 < /dev/fd/3) \
          <(command3 < /dev/fd/3) > auxfile
    } 3< auxfile
  fi
}
somefunction 12; cat auxfile

Таким образом, не вкладывайте перенаправления.

3
27.01.2020, 19:46

Теги

Похожие вопросы