Странное поведение при добавлении файла с кошкой и тройником

Одно решение проблемы из добавить к файлу одну оболочку liner?:

cat header main | tee main > /dev/null

Как заметили некоторые из комментариев, это не работает для больших файлов.

Вот пример, где он работает:

$ echo '1' > h
$ echo '2' > t
$ cat h t | tee t > /dev/null
$ cat t
1
2

И где он ломается:

$ head -1000 /dev/urandom > h
$ head -1000 /dev/urandom > t
$ cat h t | tee t > /dev/null
^C

Команда зависает, и после ее уничтожения мы остаемся с:

$ wc -l t
7470174 t

Что вызывает описанное выше поведение, когда команда застревает и добавляет строки бесконечно? Что отличается в сценарии с 1 строкой?

Ответ 1

Поведение совершенно недетерминированное. Когда вы делаете cat header main | tee main > /dev/null, происходят следующие вещи:

1) cat opens header
2) cat opens main
3) cat reads header and writes its content to stdout
4) cat reads main and writes its content to stdout
5) tee opens main for writing, truncating it
6) tee reads stdin and writes the data read into main

Порядок выше является одним из возможных, но эти события могут происходить во многих разных порядках. 5 должно предшествовать 6, 2 должно предшествовать 4, и 1 должно предшествовать 3, но вполне возможно, что порядок будет 5,1,3,2,4,6. В любом случае, если файлы большие, весьма вероятно, что шаг 5 будет выполнен до завершения шага 4, что приведет к тому, что части данных будут отброшены. Вполне возможно, что сначала выполняется шаг 5, и в этом случае все данные, ранее находящиеся в основном файле, будут потеряны.

Частный случай, который вы видите, очень вероятен в результате блокировки cat при записи и перехода в спящий режим до того, как он завершит чтение ввода. tee затем записывает больше данных в t и пытается прочитать из канала, затем переходит в режим сна, пока cat не записывает больше данных. cat записывает буфер, tee помещает его в t, и цикл повторяется.

Ответ 2

главная страница кота | tee main > /dev/null

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

Сначала вы можете поместить результат во временный файл, а затем переместить его на место:

cat header main >main.new && mv main{.new,}

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

exec 3<main && rm main && cat header - <&3 >main && exec 3<&-