Обработка одного файла как входного, так и выходного по всем каналам

Обработка одного файла как входного, так и выходного по всем каналам

Добрый вечер,

Я хотел бы отфильтровать содержимое файла с помощью некоторых команд piped, а затем записать результат обратно в тот же файл. Я знаю, я не могу сделать это так, как я это написал. Подождите ...

Это фрагмент bash-скрипта, который у меня есть.

grep '^[a-zA-Z.:]' "$filepath" \
    | sed -r '/^(rm|cd)/d' \
    | uniq -u \
    > "$filepath"

Поэтому я подумал, что смогу добиться успеха, используя замену процесса. Затем я написал:

grep '^[a-zA-Z.:]' < <(cat "$filepath") | …

Это тоже ничего не решило. Я ожидал, что подстановка процесса «сохранит» содержимое моего входного файла где-нибудь, например, во временном файле. Похоже, я тоже не понял подстановку процесса.

Я читал темы об «издании на месте», но в этих статьях освещались специальные опции некоторых двоичных файлов, таких как sed -iили , sort -oно мне нужно общее решение (я имею в виду, что оно должно подходить для любых переданных команд).

Итак, во-первых, почему «стандартный способ труб» не может этого сделать, что происходит под ним? :/И как мне решить мою проблему? Может кто-нибудь, пожалуйстаобъяснятьмне, что все это значит?

Спасибо.

решение1

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

#!/bin/sh -e
#Soak up input and tee it to arguments
st=0; tmpf=
tmpf="`mktemp`" && exec 3<>"$tmpf" || st="$?"
rm -f "$tmpf" #remove it even if exec failed; noop if mktemp failed
[ "$st" = 0 ] || exit "$st"
cat >&3
</dev/fd/3 tee "$@" >/dev/null

Вы можете использовать его так:

grep '^[a-zA-Z.:]' "$filepath" \
| sed -r '/^(rm|cd)/d' \
| uniq -u | sponge "$filepath" 

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

Другими словами, к моменту запуска grep (первой простой команды конвейера) последнее перенаправление уже обрезало входной/выходной файл.

Насколько мне известно, на самом деле нет стандартных утилит UNIX, которые делают настоящее редактирование на месте. sed -iтолько эмулирует его с помощью временного файла. Я предполагаю, что причина в том, что настоящая фильтрация на месте может легко повредить файл, если этап конвейера даст сбой.

Что касается того, что происходит внизу — оба |и <()используют системные каналы, которые принимают проходной ввод-вывод по буферу за раз. Механизм не создает временных файлов (в любом случае, не настоящих файлов (файловой системы)) и пытается избежать удержания всего ввода в памяти за раз.

решение2

Если вам нужен ввод и вывод в один и тот же файл, вы можете попробоватьгубка. Как гласит его описание:

sponge reads standard input and writes it out to the specified file. 
Unlike a shell redirect, sponge soaks up all its input before writing 
the output file. This allows constructing pipelines that read from and 
write to the same file.

Таким образом, вы можете получить что-то вроде sed '...' file | grep '...' | sponge [-a] fileотзывов отфайли вывод на тот жефайл.


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

tempfile=`mktemp tempFile.XXXX` # You can replace "tempFile" with any name you want

Это создаст временный файл с именем «tempFile» в каталоге, где запущен этот скрипт, с расширением «XXXX», где символы x заменены комбинацией текущего номера процесса и случайных букв (например, tempFile.AVm7).

Теперь вы можете изменить свой канал (или любую команду, переданную по каналу) следующим образом:

grep '^[a-zA-Z.:]' "$filepath" \
    | sed -r '/^(rm|cd)/d' \
    | uniq -u \
    > "$tempfile"

После фильтрации вы можете переместить временный файл в исходный файл следующим образом:

mv "$tempfile" "$filepath"

Это устранит ваш временный файл, и вы останетесь с отфильтрованным исходным файлом. Но иногда вы можете создать много временных файлов, которые вам могут не понадобиться и которые вы не уничтожили, поэтому хорошей идеей будет очистить ваш каталог, удалив все временные файлы после завершения вашего скрипта, если они вам больше не нужны. Вы можете написать для этого следующую процедуру:

remove_temp_files() {
    rm `find . -name "tempFile.????"`
}

Затем вы можете просто вызвать процедуру remove_temp_filesв конце сценария, удалив все временные файлы, созданные в описанном выше формате.

решение3

С использованиемЗдесь-ДокументиЗамена командыстандартный способ в этом случае:

grep '^[a-zA-Z.:]' <<IN \
    | sed -r '/^(rm|cd)/d' \
    | uniq -u \
    > "$filepath"
$(cat -- "$filepath")
IN

Что касается других вопросов, то они были объяснены во многих вопросах ранее:

Связанный контент