Чтение и запись файла: команда tee

10

Хорошо известно, что такая команда:

cat filename | some_sed_command >filename

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

Можно решить проблему следующим образом:

cat file | some_sed_command | tee file >/dev/null

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

VeryHardCoder
источник
Какова ваша главная цель? (простыми словами)
Сергей Колодяжный
@Serg просто понимает, как все работает ... Ответ, написанный kos, проясняет
ситуацию

Ответы:

10

Можно решить проблему следующим образом:

cat file | some_sed_command | tee file >/dev/null

Нет .

Скорее всего, fileбудет усечено падение, но нет никаких гарантий cat file | some_sed_command | tee file >/dev/null, не будет усечено file.

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

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

script.sh:

#!/bin/bash
for ((i=0; i<100; i++)); do
    cat >file <<-EOF
    foo
    bar
    EOF
    cat file |
        sed 's/bar/baz/' |
        tee file >/dev/null
    [ -s file ] &&
        echo 'Not truncated' ||
        echo 'Truncated'
done |
    sort |
    uniq -c
rm file
% bash script.sh
 93 Not truncated
  7 Truncated
% bash script.sh
 98 Not truncated
  2 Truncated
% bash script.sh
100 Not truncated

Поэтому никогда не используйте что-то подобное cat file | some_sed_command | tee file >/dev/null. Используйте, spongeкак предложил Оли.

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

$ cat file
foo
bar
$ for ((i=0; i<100; i++)); do <<<"$(<file)" sed 's/bar/baz/' >file; done
$ cat file
foo
baz
кос
источник
9

В sedчастности, вы можете использовать его -iаргумент на месте. Он просто сохраняет обратно в файл, который он открыл, например:

sed -i 's/ /-/g' filename

Если вы хотите сделать что-то более громкое, предполагая, что вы делаете больше, чем sed, да, вы можете буферизовать все это с помощью sponge(из moreutilsпакета), что «впитает» весь стандартный ввод перед записью в файл. Это как, teeно с меньшей функциональностью. Для базового использования, это в значительной степени замена:

cat file | some_sed_command | sponge file >/dev/null

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

Оли
источник
0

Вы можете использовать Vim в режиме Ex:

ex -sc '%!some_sed_command' -cx filename
  1. % выбрать все строки

  2. ! Команда Run

  3. x Сохранить и выйти

Стивен Пенни
источник
0

О, но spongeэто не единственный вариант; Вам не нужно получать moreutils, чтобы заставить это работать должным образом. Любой механизм будет работать, если он удовлетворяет следующим двум требованиям:

  1. Он принимает имя выходного файла в качестве параметра.
  2. Он создает выходной файл только после обработки всего ввода.

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

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

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

    shuf --output=file --random-source=/dev/zero 

Эти --random-source=/dev/zeroприемы части shufв делать свое дело , не делая перестановку на всех, так что это будет буфер ввода , не изменяя его.

Майк Накис
источник