Команда sponge: «губка» для стандартного ввода

    Все мы знаем, что при выполнении команд в шелле мы можем перенаправлять стандартный вывод на стандартный ввод других команд, а также записывать его в файл.

    Это достаточно детально описано в главе I/O Redirection в «Продвинутом руководстве по программированию на Bash» (Advanced Bash-Scripting Guide).

    В частности, иногда бывает так, что вам нужно прочитать какой-то файл, как-то его обработать (например, выбрать оттуда только те строки, которые подходят под некое регулярное выражение), и затем записать результат в тот же самый файл. Допустим, ваш файл называется «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 для файла ввода с соответствующими значениями для файла, используемого для записи стандартного вывода. Посмотреть реализацию этого подхода можно в src/cat.c.

    В 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: 123\nError: 123\n", 4096)      = 24
    write(4, "Success: 123\n", 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
    

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

    Желаю всем отличной пятницы!
    Поделиться публикацией

    Похожие публикации

    Комментарии 23

      +12
      Решение с временным файлом будет немного универсальнее — кто знает, как много данных нужно записать? А вдруг память закончится?
      Тем более, можно использовать что-то вроде:

      grep '^Success:\s ' < messages.log > messages.log_ ; mv messages.log{_,}
      


      Относительно кратко и делает ту же работу.
        +1
        Если нужно обработать много данных — то да, решение с временным файлом безусловно безопаснее.

        Но лично я бы, если бы мне нужно было делать такое часто, устал каждый раз набирать такую длинную конструкцию. Как вариант, кстати, нам же никто не мешает дописать себе в ~/.bashrc (~/.bash_profile) что-нибудь такое (wf в смысле «write file»):

        function wf() {
            local tmpfile="$@_$(< /dev/urandom tr -dc A-Za-z0-9 | head -c16)"
            cat > $tmpfile
            mv $tmpfile "$@"
        }
        

        А потом, соответственно, можно просто писать:

        grep "^Success:\s" messages.log | wf messages.log
        
          +12
          А ещё специально для вас есть mktemp
            +1
            mktemp создаёт файлы в /tmp, и если последний смонтирован на другом разделе, то mv не просто переименует файл, а будет переносить его на другой раздел. В этом плане создание временного файла в той же директории для подобных задач корректнее использования mktemp.

            Это не просто будет работать быстрее, при этом ещё и гарантируется, что итоговый файл будет полностью обработан или не обработан вообще (переименование — атомарная операция).
              +1
              mktemp умеет создавать файлы в любой директории с помощью --tmpdir
                0
                Или, как вариант, указав шаблон в качестве первого аргумента.

                mktemp temporary-file.XXXXXXXX
                
        +30
        И вот, значит, есть у нас SQL дамп на 100 гигабайт…
          +2
          Значит, не обрабатывайте его через sponge.
            +5
            … и нужно sed'ом конвертнуть базу из одной структуры таблиц в другую.
              +2
              Несколько раз подряд (:
            +1
            >Это достаточно детально описано в главе I/O Redirection в «Продвинутом руководстве по программированию на Bash» (Advanced Bash-Scripting Guide).

            Этот гайд есть частично переведенный на русский, если кому надо: www.bash-scripting.ru/abs/chunks/
            +1
            Только вот забыли добавить, что это тулза не идет в поставке стандартно почти ни с каким *nix… В отличии от sed того же, который есть почти везде сразу.
              +4
              По-моему это очевидно из:
              >это программа sponge из moreutils
              Много чего не идет в стандартной поставке… И что мешает ее установить?
                0
                То, что это не является зачаствую разумным или возможным (ставить доп. ПО), или по умолчанию считается что все везде админы?

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

                Это совет сродни «Чтобы машина держала скорость, надо включить круиз контроль. Нету? Поставьте дополнительно!»

                Sed в данном случае — стандарт практически.
                  +2
                  > или по умолчанию считается что все везде админы?
                  Ну, если человек не может установить дополнительную утилиту, то и sponge вряд ли ему нужна…

                  > Намного проще и лучше использовать универсальный функционал, а не ставить утилиты на любой чих.
                  В том-то и суть unix way: кому-то нужен один функционал, кому-то другой, для этого и существуют репозитории с возможностью доустановить то, что необходимо, а если пытаться создать универсальный дистрибутив на все случаи жизни, то его не то, что выкачивать замучаешься, но и разворачивать/настраивать/обновлять, не говоря уже о том, что большая часть функционала будет мертвым грузом…
                    +2
                    Какой-то дет. сад, ей богу.

                    У кучи разработчиков (да у 99%) например нет административных доступов на сервера с которыми они работают. Мало того, если такой горе-разработчик напишет скрипты которые используют «левую» утилиту — то получит по ушам.

                    Есть базовый, устоявшийся набор утилит который присутствует практически в любом *nix. Ставить дополнительное ПО для того, чтобы работать с буфером файлов — это мягко говоря странно, ладно бы речь шла о чем-то сложном.

                    То что вы описываете — это не unix-way, это как раз самый что ни на есть windows way — на любой чих ставить утилитку.
                      –3
                      Сочувствую вашим разработчикам…
                        +2
                        Сочувствуйте лучше себе :)

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

                        Скорее всего это придет позже, после того как начнете заниматься действительно серьезными проектами. Если начнёте.
                        +3
                        Это не тот случай. Это как раз unix-way.

                        Windows-way был бы, если бы пришлось ставить какую-то программу, которая выполняет какую-то прикладную задачу (при этом не расширяется, не автоматизируется, выполняет её плохо или выполняет только некоторый сценарий задачи), вместо написания bash файла из 3-х строчек.

                        Пакет «moreutils» доступен, наверное, в каждом дистрибьютиве. Входит в пакетную базу дистрибьютива. Это не то же самое что ставить 3rd party программу неизвестно откуда. И то что выполняет sponge — универсальная задача, подходит для скриптинга.

                        Ставить новый пакет или нет ради этого — это дело хозяйское, в разных ситуациях может быть по разному.

                        Использовать вместо sponge свой велосипед или хак с временными файлами — это как раз не unix-way (если использовать часто, и если нет требований по памяти, простоте деплоя)

                        У кучи разработчиков (да у 99%)

                        Откуда такая статистика? Помоему с потолка. Откуда вообще информация что у большинства разработчиков нет прав? Помоему тоже с потолка.
                        0
                        Ну вот у меня на работе например так. На дев сервере у меня нет прав на это. И это правильно. Я вообще программист клиент сайда. А ещё есть 100500 боевых серверов, я даже не знаю их количество. Ладно, на дев можно попросить админов поставить, но на бой то зачем тащить? На все сразу сервера. А если в скрипте заюзаю. В общем за статью спасибо большое, новое узнал. А этот спор он ни к чему.
                  +4
                  Спасибо, не знал про такую удобную штуку.
                    +2
                    Даёшь больше обзоров хороших shell утилит.

                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                    Самое читаемое