Писать в тот же файл, который читается
от kalter
Задача: изменить содержимое файла командами без создания временных файлов.
Пусть есть файл test с таким содержимым:
Изменять его будем так:
При выполнении этой команды изменённое содержимое файла будет выведено в терминал. Если попробовать записать в тот же файл:
Изменённое содержимое добавилось к актуальному. Теперь попробуем перезаписать актуальное содержимое на изменённое:
Файл test очистится. Он станет пуст.
Проблема и решение
Шелл сначала очищает содержимое файла. Уже потом sed нечего изменять и нечего записывать в файл.
Так шелл работает с редиректом >, который перезаписывает содержимое файла. С >> такой проблемы нет.
Проект moreutils предлагает утилиту sponge для решения это проблемы.
Эта утилита не делает ничего необычного, она просто пишет в переданный файл всё тем же шелловым редиректом >. Просто получается так, что сначала данные из файла читаются и передаются этой утилите через пайп, и только потом эта утилита пишет переданные ей данные в файл.
Ниже - простейшая реализация sponge, просто чтобы логика описанных процессов была предельно понятна.
Кстати, такие варианты тоже не сработают:
Не знаю почему. Так работает шелл (и скобки, и eval, - "shell built-in"). Но можно вместо shell built-in использовать что-то своё outside of shell:
Это общее решение. Есть sed-specific, которое часто показывают на StackOverflow:
Здесь используется ключ -i вместо самостоятельной возни с перенаправлениями стандартных потоков. В этом случае sed сам решает, как ему стоит изменять содержимое указанного файла.
Пусть есть файл test с таким содержимым:
- one
- two
Изменять его будем так:
- $ sed -r "s/one/three/" < test
- three
- two
При выполнении этой команды изменённое содержимое файла будет выведено в терминал. Если попробовать записать в тот же файл:
- $ sed -r "s/one/three/" < test >> test
- $ cat test
- one
- two
- three
- two
Изменённое содержимое добавилось к актуальному. Теперь попробуем перезаписать актуальное содержимое на изменённое:
- $ sed -r "s/one/three/" < test > test
Файл test очистится. Он станет пуст.
Проблема и решение
Шелл сначала очищает содержимое файла. Уже потом sed нечего изменять и нечего записывать в файл.
Так шелл работает с редиректом >, который перезаписывает содержимое файла. С >> такой проблемы нет.
Проект moreutils предлагает утилиту sponge для решения это проблемы.
test
- $ sed -r "s/one/three/" < test | sponge test
- $ cat test
- three
- two
Эта утилита не делает ничего необычного, она просто пишет в переданный файл всё тем же шелловым редиректом >. Просто получается так, что сначала данные из файла читаются и передаются этой утилите через пайп, и только потом эта утилита пишет переданные ей данные в файл.
Ниже - простейшая реализация sponge, просто чтобы логика описанных процессов была предельно понятна.
- #!/usr/bin/bash
- [[ -r "$1" ]] && cat > "$1"
Кстати, такие варианты тоже не сработают:
- $ sed -r "s/one/three/" < test | ( cat > test )
- $ sed -r "s/one/three/" < test | eval "cat > test"
Не знаю почему. Так работает шелл (и скобки, и eval, - "shell built-in"). Но можно вместо shell built-in использовать что-то своё outside of shell:
spawn_temporary_bash_script
- $ sed -r "s/one/three/" < test | spawn_temporary_bash_script "cat > test"
Это общее решение. Есть sed-specific, которое часто показывают на StackOverflow:
- $ sed -i -r "s/one/three/" test
Здесь используется ключ -i вместо самостоятельной возни с перенаправлениями стандартных потоков. В этом случае sed сам решает, как ему стоит изменять содержимое указанного файла.