Вопрос по in-place, overwrite, io-redirection, bash, pipeline – Почему на некоторых платформах передача в один файл не работает?

5

В cygwin следующий код работает нормально

$ cat junk
bat
bat
bat

$ cat junk | sort -k1,1 |tr 'b' 'z' > junk

$ cat junk
zat
zat
zat

Но в оболочке Linux (GNU / Linux) кажется, что перезапись не работает

[41] othershell: cat junk
cat
cat
cat
[42] othershell: cat junk |sort -k1,1 |tr 'c' 'z'
zat
zat
zat
[43] othershell: cat junk |sort -k1,1 |tr 'c' 'z' > junk
[44] othershell: cat junk

В обеих средах работает BASH.

Я спрашиваю об этом, потому что иногда после выполнения манипуляций с текстом из-за этого предостережения я вынужден создавать файл tmp. Но я знаю, что в Perl вы можете указать «я». признак перезаписи исходного файла после некоторых операций / манипуляций. Я просто хочу спросить, есть ли какой-нибудь надежный метод в конвейере Unix для перезаписи файла, о котором я не знаю.

Perl иsed& APOS; s-i выполнять временные манипуляции с файлами за кулисами. Dennis Williamson

Ваш Ответ

5   ответов
6

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

Даже если bash под Cygwin позволит вам сойти с рук, вы не должны на это полагаться. Общее решение состоит в том, чтобы перенаправить во временный файл и затем переименовать его, когда конвейер завершен.

11

"Useless use of cat." Don't do that. You're not actually sorting anything with sort. Don't do that. Your pipeline doesn't say what you think it does. Don't do that. You're trying to over-write a file in-place while reading from it. Don't do that.

Одна из причин, по которой вы получаете противоречивое поведение, заключается в том, что вы направляете конвейер процессу, который имеет перенаправление, а не перенаправляет вывод конвейера в целом. Разница тонкая, но важная.

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

{ sort -k1,1 | tr 'c' 'z'; } < junk > sorted_junk

Обратите внимание, что без сортировки вы можете пропуститьsort команда тоже. Тогда ваша команда может быть запущена без необходимости группирования команд:

tr 'c' 'z' < junk > sorted_junk

Сохраняйте перенаправления и конвейеры как можно более простыми. Это делает отладку ваших скриптов намного проще.

Однако, если вы все же хотите по какой-то причине злоупотреблять конвейером, вы можете использоватьsponge утилита изmoreutils пакет. На странице руководства написано:

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

Итак, ваша оригинальная командная строка может быть переписана так:

cat junk | sort -k1,1 | tr 'c' 'z' | sponge junk

и поскольку мусор не будет перезаписан до тех пор, пока sponge не получит EOF из конвейера, вы получите ожидаемые результаты.

@CodeGnome: Извините, я не собирался публиковать это. Это было только начало предложения, которое я никогда не заканчивал. Вместо этого я написал примечание об использованииsponge надhere.
Спасибо за ваш вдумчивый ответ! Я изменил ввод, чтобы прояснить свой вопрос. Исходные данные должны быть отсортированы. Но ты прав. sort ничего не делает в этом примере Alby
@nobar Они не одинаковы. Увидетьsponge(1) man page отmoreutils пакет.
sponge это какcat
0

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

Это происходит потому, что вход и выход в конвейере автоматически буферизуются (что создает впечатление, что он работает), но на самом деле он работает параллельно. Различные платформы могут буферизовать вывод по-разному (в зависимости от настроек), поэтому на некоторых вы получите пустой файл (потому что файл будет создан в начале), а на другой - с полуфабрикатом.

Решение состоит в том, чтобы использовать какой-либо метод, когда файл переопределяется только при обнаружении EOF с полным буферизованным и обработанным вводом.

Это может быть достигнуто путем:

Using utility which can soaks up all its input before opening the output file.

This can either be done by sponge (as opposite of unbuffer from expect package).

Avoid using I/O redirection syntax (which can create the empty file before starting the command).

For example using tee (which buffers its standard streams), for example:

cat junk | sort | tee junk

This would only work with sort, because it expects all the input to process the sorting. So if your command doesn't use sort, add one.

Another tool which can be used is stdbuf which modifies buffering operations for its standard streams where you can specify the buffer size.

Use text processor which can edit files in-place (such as sed or ex).

Example:

$ ex -s +'%!sort -k1' -cxa myfile.txt
$ sed -i '' s/foo/bar/g myfile.txt
3

вы можете просто использовать редактор.

ex junk << EOF
%!(sort -k1,1 |tr 'b' 'z')
x
EOF
0

вы можете заставить его работать так, как вы хотите:

$ cat junk | sort -k1,1 |tr 'b' 'z' | overwrite_file.sh junk
overwrite_file.sh
#!/usr/bin/env bash

OUT=$(cat -)

FILENAME="$*"

echo "$OUT" | tee "$FILENAME"

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

overwrite_file_no_output.sh
#!/usr/bin/env bash

OUT=$(cat -)

FILENAME="$*"

echo "$OUT" > "$FILENAME"

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