Использование встроенного расширения подстроки bash:
for f in 2015* ; do
mv "$f" "${f::4}-${f:4:2}-${f:6}"
done
Мы не знаем размер вывода cmd1
, но каналы имеют ограниченный размер буфера. Как только этот объем данных будет записан в канал, любая последующая запись будет заблокирована до тех пор, пока кто-нибудь не прочитает канал (вид вашего неудачного решения 3).
Вы должны использовать механизм, гарантирующий отсутствие блокировки. Для очень больших данных используйте временный файл. В противном случае, если вы можете позволить себе хранить данные в памяти (в конце концов, это была идея с каналами), используйте это:
result=$(cmd1) && cmd2 < <(printf '%s' "$result")
unset result
Здесь результат cmd1
сохраняется в переменной result
. ]. Если cmd1
выполнено успешно, выполняется cmd2
и передаются данные из result
. Наконец, результат
не установлен для освобождения связанной памяти.
Примечание: ранее я использовал здесь-строку ( <<< "$result"
) для передачи cmd2
данных, но Стефан Шазелас заметил, что bash
создаст временный файл, который вам не нужен.
Ответы на вопросы в комментариях:
Да, команды можно объединять в цепочки вволю:
result=$(cmd1) \
&& результат=$(cmd2 < <(printf '%s' "$result")) \
&& результат=$(cmd3 < <(printf '%s' "$result")) \
...
&& cmdN < <(printf '%s' "$result")
неустановленный результат
Нет, приведенное выше решение не подходит для двоичных данных, потому что:
$(...)
съедает все завершающие символы новой строки.\0
) в результате подстановки команд (например, Bash отбрасывает их).Да, чтобы обойти все эти проблемы с двоичными данными, вы можете использовать кодировщик, например base64
(или uuencode
, или самодельный, который обрабатывает только символы NUL. и завершающие символы новой строки):
результат=$(cmd1 > >(base64)) && cmd2 <<(printf '%s' "$result" | base64 -d)
неустановленный результат
Здесь мне пришлось использовать замену процесса ( >(...)
), чтобы сохранить выходное значение cmd1
нетронутым.
Тем не менее, опять же, это кажется довольно хлопотным только для того, чтобы гарантировать, что данные не будут записываться на диск. Промежуточный временный файл является лучшим решением. См. ответ Стефана, который отвечает на большинство ваших опасений по этому поводу.
Весь смысл передачи команд состоит в том, чтобы запускать их одновременно с одним чтением вывода другого. Если вы хотите запускать их последовательно, и если мы сохраним метафору водопровода, вам нужно передать вывод первой команды в ведро (сохранить его), а затем очистить ведро в другой команде.
Но делать это с помощью каналов означает иметь два процесса для первой команды (команда и другой процесс, считывающий ее вывод с другого конца канала для сохранения в ведре) и два для второго (один из которых очищает ведро). в один конец канала, чтобы команда читала его с другого конца).
Для корзины вам понадобится либо память, либо файловая система. Память плохо масштабируется, и вам нужны каналы. Файловая система имеет гораздо больше смысла. Вот для чего /tmp
.Обратите внимание, что диски, скорее всего, никогда не увидят данные, поскольку данные могут не быть сброшены туда намного позже (после того, как вы удалите временный файл), и даже если это так, они, скорее всего, все еще останутся в памяти (кэшируются). А когда это не так, тогда данные были бы слишком большими, чтобы в первую очередь поместиться в памяти.
Обратите внимание, что временные файлы постоянно используются в оболочках. В большинстве оболочек здесь документы и здесь строки реализованы с помощью временных файлов.
В:
cat << EOF
foo
EOF
Большинство оболочек создают временный файл, открывают его для записи и чтения, удаляют его, заполняют foo
, а затем запускают cat
со своим стандартным вводом. продублировано из фд открытого для чтения. Файл удаляется еще до того, как он заполнится (это дает системе понять, что все, что там написано, не должно пережить потерю питания).
Вы можете сделать то же самое здесь с:
tmp=$(mktemp) && {
rm -f -- "$tmp" &&
cmd1 >&3 3>&- 4<&- &&
cmd2 <&4 4<&- 3>&-
} 3> "$tmp" 4< "$tmp"
Тогда вам не нужно беспокоиться об очистке, так как файл удаляется с самого начала. Нет необходимости в дополнительных процессах для получения данных в корзинах и из них, cmd1
и cmd2
делают это сами по себе.
Если вы хотите сохранить вывод в памяти, использование оболочки для этого не будет хорошей идеей. Первые оболочки, отличные от zsh
, не могут хранить произвольные данные в своих переменных. Вам нужно будет использовать какую-то форму кодирования. И затем, чтобы передать эти данные, вам придется дублировать их в памяти несколько раз, если не записывать их на диск при использовании здесь-документа или здесь-строки.
Вместо этого вы можете использовать perl
, например:
perl -MPOSIX -e '
sub status() {return WIFEXITED($?) ? WEXITSTATUS($?) : WTERMSIG($?) | 128}
$/ = undef;
open A, "-|", "cmd1" or die "open A: $!\n";
$out = <A>;
close A;
$status = status;
exit $status if $status != 0;
open B, "|-", "cmd2" or die "open B: $!\n";
print B $out;
close B;
exit status'
Во-первых, ваш пример false | echo ok
не имеет смысла, поскольку false
ничего не выводит на свой стандартный вывод, а echo
не читает со своего стандартного ввода. «Решение» этого false && echo ok
.
cmd1 && cmd2
Это запустит cmd1
и не запустит cmd2
, пока cmd1
не завершит успешное выполнение.
В конвейере, таком как
cmd1 | cmd2
две команды всегда запускаются одновременно (это то, что вы заметили в своем «Неудачное решение 1» ). Их синхронизирует чтение cmd2
из вывода cmd1
. Конвейер — это способ передачи вывода одной программы во ввод другой, параллельно работающей программы.
Чтобы имитировать, что cmd1
выводит что-то, что читает cmd2
, но чтобы избавиться от параллелизма, вам нужно сохранить вывод из cmd1
во временный файл, который cmd2
читает:
cmd1 >outfile && cmd2 <outfile
Временный файл может обрабатываться следующим образом:
trap 'rm -f "$tmpfile"' EXIT
tmpfile=$(mktemp)
cmd1 >"$tmpfile" && cmd2 <"$tmpfile"
Это устанавливает ловушку, которая срабатывает при выходе из оболочки. Ловушка удалит временный файл.
Если у вас есть $TMPDIR
в файловой системе памяти, вы не понесете никаких штрафов за ввод-вывод за запись на диск.
Если вас беспокоит размер файла, то вы будете вынуждены хранить его на диске во что бы то ни стало (канал также не сможет удерживать содержимое, это то, что вы заметили в своем " Неудачное решение 3" ).
Глядя на решение xhienne для Bash:
result=$(cmd1) && cmd2 <<< "$result"
unset result
Это работает, если результатом является текст, который не заканчивается пустыми строками, но не работает, если он содержит нулевые байты (они будут отброшеныbash
).
Чтобы смягчить это, мы могли бы base64 -закодировать результат:
set -o pipefail # ksh/zsh/bash
result=$( cmd1 | base64 ) && base64 -d <<<"$result" | cmd2
unset result
Это ужасная идея с точки зрения использования памяти и процессора.особенно если результат большой (, кодировка base64 $result
будет на треть больше, чем двоичная ). Гораздо лучше записать двоичный результат на диск и прочитать его оттуда.
Также обратите внимание, что bash
реализует <<<
с использованием временного файла в любом случае.
Вот откровенно ужасный вариант сшивания разных инструментов изmoreutils
:
chronic sh -c '! { echo 123 ; false ; }' | mispipe 'ifne -n false' 'ifne echo ok'
Это все еще не совсем то, что вам нужно :он возвращает 1 в случае неудачи и ноль в противном случае. Однако он не запускает вторую команду до тех пор, пока первая не завершится успешно, он возвращает неудачный или успешный код в зависимости от того, сработала первая команда или нет, и не использует файлы.
Более общая версия:
chronic sh -c '! '"$CMD1" | mispipe 'ifne -n false' "ifne $CMD2"
Это объединяет три инструмента moreutils:
chronic
спокойно выполняет команду, если она не завершается ошибкой. В этом случае мы запускаем оболочку для запуска вашей первой команды, чтобы мы могли инвертировать результат успеха/неудачи :, он будет запускать команду тихо , если она не удалась, и печатать вывод в конец, если это удается. mispipe
соединяет две команды вместе, возвращая статус выхода первой. Это похоже на эффект set -o pipefail
. Команды предоставляются в виде строк, чтобы их можно было отличить друг от друга. ifne
запускает программу, если стандартный ввод не пуст -или пуст с -n
. Мы используем его дважды:
Первый — ifne -n false
. Это запускает false
и использует его в качестве кода выхода, если и только если ввод пуст (, что означает, что chronic
съел его, что означает, что cmd1
не удалось ).
Когда ввод не пуст, он не запускается false
, пропускает ввод, как cat
, и выходит с 0. Вывод будет передан в следующую команду с помощью mispipe
.
Второй — ifne cmd2
. Это запустит cmd2
, если вход не является -пустым . Эти входные данные являются выходными данными ifne -n false
, которые будут не -пустыми точно в тот момент, когда выходные данные chronic
не были -пустыми,что происходит, когда команда выполнена успешно.
Когда вход пуст, cmd2
никогда не запускается, а ifne
выходит из нуля. mispipe
все равно отбрасывает выходное значение.
В этом подходе есть (как минимум )два оставшихся недостатка:
cmd1
, уменьшая его до логического значения true/false. Если код выхода имеет смысл, он потерян. Можно было бы сохранить код в файл в команде sh
и повторно -загрузить его позже(ifne -n sh -c 'read code <FILENAME ; rm -f FILENAME; exit $code'
или что-то в этом роде ), если это необходимо. cmd1
когда-нибудь удастся добиться успеха без результата, все равно все развалится. Плюс, конечно же, несколько довольно -редких команд, собранных вместе, тщательно процитированных, с неочевидным -значением.