С оболочками POSIX:
string=whateverDELIMrestDELIMmore
before_first_DELIM=${string%%DELIM*}
before_last_DELIM=${string%DELIM*}
after_first_DELIM=${string#*DELIM}
after_last_DELIM=${string##*DELIM}
В
{ err=$(exec 2>&1 >&3; ls -ld /x /bin); exec 0<&3; out=$(cat); } 3>&1
{... } 3>&1
клонирует fd 1 в fd 3. Это просто означает, что fd 3 теперь указывает на тот же ресурс, (то же описание открытого файла ), что и то, на что указывает fd 1. Если вы запустили это с терминала, это, вероятно, будет fd, открытый в режиме чтения + записи для терминального устройства.
После exec 0<&3
fds 0, 1 и 3 указывают на одно и то же описание открытого файла (, созданное, когда ваш эмулятор терминала открыл подчиненную сторону пары псевдотерминалов -, которую он создал ранее. выполнение вашей оболочки в случае запуска команды в терминальном случае выше ).
Затем в out=$(cat)
для процесса, выполняющего cat
, $(...)
изменяет fd 1 на записывающий конец канала, в то время как 0 по-прежнему является устройством tty. Таким образом, cat
будет считываться с терминального устройства, поэтому то, что вы печатаете на клавиатуре (, и если бы это было не терминальное устройство, вы, вероятно, получили бы ошибку, так как fd, вероятно, был открыт для записи -только режим ).
Чтобы cat
читал то, что ls
пишет на своем стандартном выводе, вам нужно, чтобы ls
стандартный вывод и cat
стандартный ввод были двумя концами механизма IPC, такого как канал, пара сокетов или псевдотерминальная пара -.. Например, ls
stdout — конец канала для записи, а cat
stdin — конец для чтения.
Но вам также потребуется, чтобы ls
и cat
выполнялись одновременно, а не один за другим, так как это механизм межпроцессного взаимодействия (IPC -между процессами ).
Так как каналы могут содержать некоторые данные (64 КиБ по умолчанию в текущих версиях Linux ), вам бы сошли с рук короткие выходные данные, если бы вам удалось создать второй канал, но для больших выходы, вы столкнетесь с взаимоблокировками, ls
будет зависать, когда канал заполнен, и будет зависать, пока что-то не опустошит канал, но cat
может очистить канал только после возврата ls
.
Кроме того,только yash
имеет необработанный интерфейс для pipe()
, который вам потребуется для создания второго канала для чтения из ls
stdout (другого канала для stderr, созданного конструкцией $(...)
).
В yash, вы бы сделали:
{ out=$(ls -d / /x 2>&3); exec 3>&-; err=$(exec cat <&4); } 3>>|4
Где3>>|4
(yash -специфическая функция )создает этот второй канал с концом записи на fd 3 и концом чтения на fd 4.
Но опять же, если вывод stderr больше, чем размер канала, это зависнет. Мы эффективно используем канал как временный файл в памяти, а не как канал.
Чтобы действительно использовать каналы, нам нужно начать ls
с stdout, являющегося концом записи одного канала, и stderr, являющимся концом записи другого канала, а затем оболочка одновременно считывает другие концы этих каналов, как данные приходят (не один за другим, иначе вы снова столкнетесь с мертвыми -блокировками )для сохранения в две переменные.
Чтобы иметь возможность читать из этих двух fd по мере поступления данных, вам потребуется оболочка с поддержкой select()
/poll()
. zsh
является такой оболочкой, но у нее нет yash
функции перенаправления конвейера ¹, поэтому вам нужно будет использовать именованные каналы (, чтобы управлять их созданием, разрешениями и очисткой. )и использовать сложный цикл с zselect
/ sysread
...
¹ Однако в Linux вы можете использовать тот факт, что /proc/self/fd/x
в канале ведет себя как именованный канал, поэтому вы можете:
#! /bin/zsh
zmodload zsh/zselect
zmodload zsh/system
(){exec {wo}>$1 {ro}<$1} <(:) # like yash's wo>>|ro (but on Linux only)
(){exec {we}>$1 {re}<$1} <(:)
ls -d / /x >&$wo 2>&$we &
exec {wo}>&- {we}>&-
out= err=
o_done=0 e_done=0
while ((! (o_done && e_done))) && zselect -A ready $ro $re; do
if ((${#ready[$ro]})); then
sysread -i $ro && out+=$REPLY || o_done=1
fi
if ((${#ready[$re]})); then
sysread -i $re && err+=$REPLY || e_done=1
fi
done
Это похоже на базовое ограничение синтаксиса присваивания переменных и побочные -эффекты порождения подоболочек оболочки. Вы можете захватить либо stderr, либо stdout, но не оба :, другой поток необходимо перенаправить в файл (, возможно, в FIFO ).
# a function for testing
your_command() { sh -c 'echo "this is stdout"; echo "this is stderr" >&2'; }
errfile=$(mktemp)
out=$( your_command 2>|"$errfile" )
err=$(< "$errfile")
rm "$errfile"
echo "out: $out"
echo "err: $err"