Назад | Перейти на главную страницу

Linux: Как использовать файл как ввод и вывод одновременно?

Я только что запустил в bash следующее:

uniq .bash_history > .bash_history

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

Думаю, мне нужен способ прочитать весь файл перед записью в него. Как это сделать?

PS: Я, очевидно, думал об использовании временного файла, но ищу более элегантное решение.

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

echo "$(uniq .bash_history)" > .bash_history

должен иметь желаемый результат. Подоболочка запускается до открытия .bash_history для записи. Как объясняется в ответе Фила П., к тому времени, когда .bash_history будет прочитан в исходной команде, он уже будет усечен оператором '>'.

Я рекомендую использовать sponge из Moreutils. На странице руководства:

DESCRIPTION
  sponge  reads  standard  input  and writes it out to the specified file. Unlike
  a shell redirect, sponge soaks up all its input before opening the output file.
  This allows for constructing pipelines that read from and write to the same 
  file.

Чтобы применить это к вашей проблеме, попробуйте:

uniq .bash_history | sponge .bash_history

Проблема в том, что ваша оболочка настраивает конвейер команд перед запуском команд. Дело не в «вводе и выводе», а в том, что содержимое файла исчезло еще до запуска uniq. Это выглядит примерно так:

  1. Оболочка открывает > выходной файл для записи, усекая его
  2. Оболочка настраивает использование файлового дескриптора 1 (для стандартного вывода) для этого вывода.
  3. Оболочка выполняет uniq, возможно что-то вроде execlp («uniq», «uniq», «.bash_history», NULL)
  4. запускается uniq, открывает .bash_history и ничего там не находит

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

Еще один трюк, чтобы сделать это без использования sponge, это следующая команда:

{ rm .bash_history && uniq > .bash_history; } < .bash_history

Это один из читов, описанных в отличной статье Редактирование файлов «на месте» на backreference.org.

По сути, он открывает файл для чтения, а затем «удаляет» его. Однако на самом деле он не удален: на него указывает дескриптор открытого файла, и пока он остается открытым, файл все еще существует. Затем он создает новый файл с тем же именем и записывает в него уникальные строки.

Недостаток этого решения: если uniq не работает по какой-то причине, ваша история исчезнет.

использовать губку из Moreutils

uniq .bash_history | sponge .bash_history

В качестве интересного лакомого кусочка sed также использует временный файл (он делает это за вас):

$ strace sed -i 's/foo/bar/g' foo    
open("foo", O_RDONLY|O_LARGEFILE)       = 3
...
open("./sedPmPv9z", O_RDWR|O_CREAT|O_EXCL|O_LARGEFILE, 0600) = 4
...
read(3, "foo\n"..., 4096)               = 4
write(4, "bar\n"..., 4)                 = 4
read(3, ""..., 4096)                    = 0
close(3)                                = 0
close(4)                                = 0
rename("./sedPmPv9z", "foo")            = 0
close(1)                                = 0
close(2)                                = 0

Описание:
Временный файл ./sedPmPv9z становится fd 4, а foo files становится fd 3. Операции чтения выполняются на fd 3, а записи - на fd 4 (временный файл). Затем файл foo перезаписывается временным файлом при вызове переименования.

это sed скрипт удаляет соседние дубликаты. С -i вариант, он выполняет модификацию на месте. Это из sed info файл:

sed -i 'h;:b;$b;N;/^\(.*\)\n\1$/ {g;bb};$b;P;D' .bash_history

Другое решение:

uniq file 1<> líneas.txt

Временный файл - это почти все, если только данная команда не поддерживает редактирование на месте (uniq нет - некоторые sedя делаю (sed -i)).

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

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

  2. ! Команда выполнения

  3. x сохранить и закрыть

Вы также можете использовать tee, используя вывод uniq в качестве ввода:

uniq .bash_history | tee .bash_history