- PVSM.RU - https://www.pvsm.ru -
Все мы знаем, что при выполнении команд в шелле мы можем перенаправлять [1] стандартный вывод [2] на стандартный ввод других команд, а также записывать его в файл.
Это достаточно детально описано в главе I/O Redirection [3] в «Продвинутом руководстве по программированию на Bash» (Advanced Bash-Scripting Guide [4]).
В частности, иногда бывает так, что вам нужно прочитать какой-то файл, как-то его обработать (например, выбрать оттуда только те строки, которые подходят под некое регулярное выражение), и затем записать результат в тот же самый файл. Допустим, ваш файл называется «messages.log», и вы хотите оставить в нём только те строки, которые начинаются со слова «Success», двоеточия и пробела (а все остальные строки убрать).
Можно предположить, что для этого подойдёт такая команда:
grep "^Success:s" messages.log > messages.log
Но это предположение окажется неправильным — при выполнении этой строчки файл messages.log будет открыт на запись и очищен ещё до того, как grep начнёт его просматривать.
Впрочем, интересно то, что когда grep всё-таки будет запущен, он обнаружит, что вывод перенаправляется в тот же файл, который он пытается прочитать, и сразу же завершится со следующим сообщением:
grep: input file ‘messages.log’ is also the output
То же самое делает и GNU cat (попробуйте выполнить cat messages.log > messages.log):
cat: messages.log: input file is output file
Это делается путём сравнения устройства и inode [5] для файла ввода с соответствующими значениями для файла, используемого для записи стандартного вывода. Посмотреть реализацию этого подхода можно в src/cat.c [6].
В BSD cat таких проверок, кстати, не предусмотрено, но в данном случае это не столь важно: файл так или иначе уже очищен, поэтому читать и записывать нечего, так что cat просто завершится.
Однако возьмём другой пример:
cat messages.log >> messages.log
В данном случае мы не очищаем messages.log, а дописываем вывод команды cat в конец файла. И если cat проверит, что эти два файла совпадают, и завершится, то файл останется в том же состоянии, а пользователь увидит ошибку. А вот если такой проверки нет, то cat войдёт в цикл и будет дополнять файл до тех пор, пока не закончится место или пока пользователь не завершит процесс.
А теперь давайте подумаем, как можно всё-таки записать вывод в тот же файл, который мы читаем. Очевидное решение — это использовать временный файл. То есть:
mv messages.log tmpmessages.log
grep "^Success:s" tmpmessages.log > messages.log
rm tmpmessages.log
Нельзя сказать, что это очень удобно, но, по крайней мере, задача таким образом вполне себе решается.
Ещё один вариант — мы можем использовать sed.
sed -i -n -e '/^Success:s/{p}' messages.log
Но это решение, конечно, не слишком универсально — ведь выбор строк, совпадающих по регулярному выражению, это лишь одна из многих задач, связанных с обработкой текста. К тому же, синтаксис в данном случае получается уже значительно сложнее.
Кстати, на самом деле sed тоже использует временный файл, в этом можно убедиться, посмотрев на вывод strace:
open("messages.log", O_RDONLY) = 3 … open("./sedWiaEAG", O_RDWR|O_CREAT|O_EXCL, 0600) = 4 … read(3, "Success: 123nError: 123n", 4096) = 24 write(4, "Success: 123n", 13) = 13 read(3, "", 4096) = 0 … close(3) = 0 … close(4) = 0 … rename("./sedWiaEAG", "messages.log") = 0 close(1) = 0 close(2) = 0 exit_group(0) = ?
Очевидно, что нужно иметь возможность как-то обойтись вообще без промежуточных файлов. И такая возможность есть — это программа sponge из moreutils.
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 constructing pipelines that read from and write to the same file.
sponge читает стандартный ввод и записывает его в указанный файл. В отличие от перенаправлений командной оболочки, sponge «впитыает» весь переданный ввод перед тем, как открыть файл, в который его требуется записать. Это позволяет использовать такие конвейеры, где чтение происходит из того же файла, в который осуществляется запись.
Итак, используя sponge, мы можем убрать из нашего примера перенаправление командной оболочки, и, вместо этого, передать имя файла, в который мы хотим передать результат, в качестве аргумента для команды sponge. Вывод команды grep мы передаём с помощью конвейера (pipe).
grep "^Success:s" messages.log | sponge messages.log
В принципе, всю блогозапись можно было бы сократить до этого примера, но, я думаю, так получилось интереснее, и, возможно, удалось даже рассказать о каких-то нюансах, о которых кто-то из читателей раньше не знал.
Желаю всем отличной пятницы!
Автор: MaGIc2laNTern
Источник [7]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/linux/33161
Ссылки в тексте:
[1] перенаправлять: http://ru.wikipedia.org/wiki/%D0%9F%D0%B5%D1%80%D0%B5%D0%BD%D0%B0%D0%BF%D1%80%D0%B0%D0%B2%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5_%28%D0%BF%D1%80%D0%BE%D0%B3%D1%80%D0%B0%D0%BC%D0%BC%D0%BD%D0%BE%D0%B5_%D0%BE%D0%B1%D0%B5%D1%81%D0%BF%D0%B5%D1%87%D0%B5%D0%BD%D0%B8%D0%B5%29
[2] стандартный вывод: http://ru.wikipedia.org/wiki/%D0%A1%D1%82%D0%B0%D0%BD%D0%B4%D0%B0%D1%80%D1%82%D0%BD%D1%8B%D0%B5_%D0%BF%D0%BE%D1%82%D0%BE%D0%BA%D0%B8
[3] I/O Redirection: http://www.tldp.org/LDP/abs/html/io-redirection.html
[4] Advanced Bash-Scripting Guide: http://www.tldp.org/LDP/abs/html/
[5] inode: http://ru.wikipedia.org/wiki/Inode
[6] src/cat.c: http://git.savannah.gnu.org/cgit/coreutils.git/tree/src/cat.c#n706
[7] Источник: http://habrahabr.ru/post/178141/
Нажмите здесь для печати.