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


55

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

uniq .bash_history > .bash_history

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

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

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


Это потому, что файлы открываются справа налево. См. Также stackoverflow.com/questions/146435/…
WheresAlice

Вы должны записать вывод в новый файл в том же каталоге и переименовать его поверх старого файла. Любой другой подход может привести к потере ваших данных, если они будут прерваны на полпути. Некоторые инструменты могут скрыть этот шаг от вас.
Касперд

Или bashне будет помещать последовательные дубликаты в свою историю, если вы установите HISTCONTROL для включения ignoredups; см. справочную страницу.
dave_thompson_085

пожалуйста, подумайте об изменении ответа на этот. serverfault.com/a/557566/130392
23inhouse

Ответы:


49

Я рекомендую использовать 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

6
Это как кошка, но с возможностями для сосания: D
MilliaLover

77

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

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

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


15
Я обычно не фанат ответов, которые углубляют древний вопрос, у которого уже есть действительный, принятый ответ - но это изящно, хорошо написано и дает веские аргументы в пользу его необходимости (облегченная среда); для меня это действительно добавляет что-то к существующему набору ответов. Добро пожаловать в SF, Харт (вы были здесь месяц, но я думаю, что это ваша первая важная публикация). Я надеюсь прочитать больше ответов от вас, как этот!
MadHatter

4
Это лучшее решение. Мне пришлось использовать подоболочку $()вместо кавычек из-за некоторых проблем с выходом из строя.
CMCDragonkai

3
Мне интересно, если это решение масштабируется до больших файлов, скажем ... 20 или 50 ГБ.
Амит Найду

1
Это действительно должен быть ответ.
maxywb

1
Я использовал этот ответ, чтобы сделать echo "$(fmt -p '# ' -w 50 readme.txt)" > readme.txtсегодня. Долго искал вокруг элегантное решение. Большое спасибо, @Hart Simha!
Шредалерт

12

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

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

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


10

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

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

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

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

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



3

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

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

Сед все еще использует временный файл, добавил ответ с straceиллюстрацией (не то, чтобы это действительно имело значение) :-)
Кайл Брандт

3
@Kyle: правда, но "с глаз долой, с ума". Лично я бы использовал явный временный файл, поскольку что-то вроде этого process input > tmp && mv tmp inputгораздо проще и более читабельно, чем использование sedхитрости просто для того, чтобы избежать временного файла, и он не будет перезаписывать мой оригинал, если он потерпит неудачу (я не знаю, будет ли sed -iнеудача изящной - я хотел бы думаю, что будет, хотя). Кроме того, есть много вещей, которые вы можете сделать с помощью метода output-to-temp-file, который нельзя сделать на месте без чего-то более сложного, чем этот sedскрипт. Я знаю, что вы знаете все это, но это может принести пользу некоторым зрителям.
Деннис Уильямсон

3

Как интересный трюк, 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файлы становятся Fd 3. Операций чтения на дескрипторе 3, и запись на Fd 4 (временный файл). Затем файл foo перезаписывается временным файлом в вызове переименования.



0

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


0

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

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

  2. ! Команда Run

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


Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.