Писать в тот же файл, который читается

от
Linux    bash, coreutils

Задача: изменить содержимое файла командами без создания временных файлов.

Пусть есть файл test с таким содержимым:
  1. one
  2. two

Изменять его будем так:
  1. $ sed -r "s/one/three/" < test
  2. three
  3. two

При выполнении этой команды изменённое содержимое файла будет выведено в терминал. Если попробовать записать в тот же файл:

  1. $ sed -r "s/one/three/" < test >> test
  2. $ cat test
  3. one
  4. two
  5. three
  6. two

Изменённое содержимое добавилось к актуальному. Теперь попробуем перезаписать актуальное содержимое на изменённое:

  1. $ sed -r "s/one/three/" < test > test

Файл test очистится. Он станет пуст.

Проблема и решение
Шелл сначала очищает содержимое файла. Уже потом sed нечего изменять и нечего записывать в файл.

Так шелл работает с редиректом >, который перезаписывает содержимое файла. С >> такой проблемы нет.

Проект moreutils предлагает утилиту sponge для решения это проблемы.

test

  1. $ sed -r "s/one/three/" < test | sponge test
  2. $ cat test
  3. three
  4. two

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

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

  1. #!/usr/bin/bash
  2.  
  3. [[ -r "$1" ]] && cat > "$1"



Кстати, такие варианты тоже не сработают:

  1. $ sed -r "s/one/three/" < test | ( cat > test )

  1. $ sed -r "s/one/three/" < test | eval "cat > test"

Не знаю почему. Так работает шелл (и скобки, и eval, - "shell built-in"). Но можно вместо shell built-in использовать что-то своё outside of shell:

spawn_temporary_bash_script

  1. $ sed -r "s/one/three/" < test | spawn_temporary_bash_script "cat > test"



Это общее решение. Есть sed-specific, которое часто показывают на StackOverflow:

  1. $ sed -i -r "s/one/three/" test

Здесь используется ключ -i вместо самостоятельной возни с перенаправлениями стандартных потоков. В этом случае sed сам решает, как ему стоит изменять содержимое указанного файла.
  • +3
  • views 4348