Лучший способ изменить файл при использовании труб? - PullRequest
8 голосов
/ 19 января 2010

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

cat file | some_script > file

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

Возможно, существует стандартная команда оболочки, которая будет буферизовать весь поток до достижения EOF? Что-то вроде:

cat file | bufferUntilEOF | script > file

Идеи

Ответы [ 8 ]

4 голосов
/ 19 января 2010

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

4 голосов
/ 19 января 2010

Вы ищете губка .

3 голосов
/ 19 января 2010

Как и многие другие, я люблю использовать временные файлы. Я использую идентификатор процесса оболочки как часть временного имени, чтобы при одновременном запуске нескольких копий сценария они не конфликтовали. Наконец, я тогда только перезаписываю исходный файл, если сценарий выполнен успешно (используется короткое замыкание логического оператора - это немного плотно, но очень удобно для простых командных строк). Если сложить все это вместе, это будет выглядеть так:

some_script < file > smscrpt.$$ && mv smscrpt.$$ file

Это оставит временный файл в случае сбоя команды. Если вы хотите устранить ошибку, вы можете изменить это на

some_script < file > smscrpt.$$ && mv smscrpt.$$ file || rm smscrpt.$$

Кстати, я избавился от плохого использования cat и заменил его на перенаправление ввода.

2 голосов
/ 19 января 2010

Использование mktemp(1) или tempfile(1) избавляет вас от необходимости придумывать уникальное имя файла.

1 голос
/ 09 июля 2015

В ответ на вопрос ОП выше об использовании sponge без внешних зависимостей и использовании ответа @D.Shawley вы можете получить эффект губки только с зависимость от gawk, что не редкость в Unix или Unix-подобных системах:

cat foo | gawk -voutfn=foo '{lines[NR]=$0;} END {if(NR>0){print lines[1]>outfn;} for(i=2;i<=NR;++i) print lines[i] >> outfn;}'

Проверка для NR>0 - усечение входного файла.

Чтобы использовать это в сценарии оболочки, измените -voutfn=foo на -voutfn="$1" или любой другой синтаксис, используемый вашей оболочкой для аргументов имени файла. Например:

#!/bin/bash
cat "$1" | gawk -voutfn="$1" '{lines[NR]=$0;} END {if(NR>0){print lines[1]>outfn;} for(i=2;i<=NR;++i) print lines[i] >> outfn;}'

Обратите внимание, что в отличие от реального sponge, это может быть ограничено размером ОЗУ. sponge фактически буферизует во временный файл, если необходимо.

1 голос
/ 19 января 2010

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

{ lines[NR] = $0; }
END {
    for (line_no=1; line_no<=NR; ++line_no) {
        print lines[line_no];
    }
}

Вы можете свернуть его в одну строку, если хотите:

cat file | awk '{lines[NR]=$0;} END {for(i=1;i<=NR;++i) print lines[i];}' > file

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

1 голос
/ 19 января 2010

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

Это почти побеждает назначение конвейеров для их буферизации.

0 голосов
/ 04 июля 2019

Я думаю, вам нужно использовать mktemp. Примерно так будет работать:

FILE=example-input.txt
TMP=`mktemp`
some_script <"$FILE" >"$TMP"
mv "$TMP" "$FILE"
...