Перенаправление с `>>` эквивалентно `>`, когда целевой файл еще не существует?

6628
Kamil Maciorowski

Рассмотрим оболочку типа Bash или sh. Основное различие между >и >>проявляется в случае, когда целевой файл существует:

  • > обрезает файл до нулевого размера, затем записывает;
  • >> не усекает, пишет (добавляет) в конец файла.

Если файл не существует, он создается с нулевым размером; затем написано Это верно для обоих операторов. Может показаться, что операторы эквивалентны, когда целевой файл еще не существует.

Они действительно?

79

1 ответ на вопрос

107
Kamil Maciorowski

ТЛ; др

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


Полный ответ

(Примечание: все мои тесты выполнены на Debian GNU / Linux 9).

Еще одно отличие

Нет, они не эквивалентны. Есть еще одно отличие. Он может проявляться независимо от того, существовал ли ранее целевой файл или нет.

Чтобы наблюдать это, запустите процесс, который генерирует данные и перенаправьте в файл с помощью >или >>(например pv -L 10k /dev/urandom > blob). Позвольте ему работать и измените размер файла (например, с помощью truncate). Вы увидите, что >сохраняет свое (растущее) смещение, в то время как >>всегда добавляет к концу.

  • Если вы усекаете файл до меньшего размера (это может быть нулевой размер)
    • >не волнует, он напишет с желаемым смещением, как будто ничего не произошло; сразу после того, как усечение смещения выходит за пределы конца файла, это приведет к тому, что файл восстановит свой старый размер и будет увеличиваться, пропущенные данные будут заполнены нулями (разреженным, если это возможно);
    • >> добавится к новому концу, файл вырастет из усеченного размера.
  • Если вы увеличите файл
    • >не волнует, он напишет с желаемым смещением, как будто ничего не произошло; просто после изменения размера смещение находится где-то внутри файла, это приведет к тому, что файл на некоторое время перестанет расти, пока смещение не достигнет нового конца, тогда файл будет расти нормально;
    • >> добавится в новый конец, файл увеличится в увеличенном размере.

Другой пример - добавить (с отдельным >>) что-то дополнительное, когда запущен процесс генерации данных и записи в файл. Это похоже на увеличение файла.

  • Процесс генерации с >запишет с желаемым смещением и перезапишет дополнительные данные в конце концов.
  • Процесс генерации с >>пропустит новые данные и добавит их после (может возникнуть условие гонки, два потока могут быть чередованы, но данные не должны быть перезаписаны).

пример

Имеет ли это значение на практике? Есть такой вопрос :

Я запускаю процесс, который выдает большой объем вывода на стандартный вывод. Отправка всего этого в файл [...] Могу ли я использовать какую-нибудь программу ротации журналов?

Этот ответ говорит, что решение logrotateс copytruncateопцией, которая действует так:

Усечение исходного файла журнала после создания копии вместо перемещения старого файла журнала и, при необходимости, создания нового.

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

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

В этом случае использование >>вместо >значительно лучше, даже если целевой файл еще не создан.


Спектакль

Как мы видим, два оператора действуют по-разному не только в начале, но и позже. Это может вызвать некоторую (незначительную?) Разницу в производительности. На данный момент у меня нет значимых результатов тестов, чтобы поддержать или опровергнуть их, но я думаю, что вы не должны автоматически предполагать, что их производительность в целом одинакова.

Таким образом, `>>` по сути "всегда ищет конец файла", в то время как `>` поддерживает указатель на последнее записанное местоположение. Кажется, что в их работе может быть какая-то тонкая разница в производительности ... Mokubai 5 лет назад 9
@Mokubai Хорошо сказано. Я собираюсь использовать ваше первое предложение как tl; dr, если вы не возражаете. Kamil Maciorowski 5 лет назад 0
Не беспокойся, дерзай. ;) Mokubai 5 лет назад 0
Где мы можем найти некоторую документированную ссылку о том, как отличаются `>` и `>>`? jjmontes 5 лет назад 0
На уровне системных вызовов `>>` использует флаг [`O_APPEND` для` open () `] (http://man7.org/linux/man-pages/man2/open.2.html). На самом деле, `>` использует `O_TRUNC`, а` >> `- нет. Сочетание `O_TRUNC | O_APPEND` также был бы возможен, язык оболочки просто не предоставляет эту функцию. ilkkachu 5 лет назад 10
@jjmontes, стандартным источником будет POSIX: http://pubs.opengroup.org/onlinepubs/9699919799.2018edition/utilities/V3_chap02.html#tag_18_07, но, конечно, в руководстве Bash также есть описания операторов перенаправления, включая нестандартные операторы те, которые он поддерживает: https://www.gnu.org/software/bash/manual/html_node/Redirections.html ilkkachu 5 лет назад 3
@ilkkachu Я нашел это интересным, так как он объясняет подробности об O_APPEND, о которых я подумал после вашего комментария :): https://stackoverflow.com/questions/1154446/is-file-append-atomic-in-unix jjmontes 5 лет назад 2
@KamilMaciorowski причина, по которой я подозреваю, что может быть небольшая разница в производительности, заключается в том, что `всегда стремиться к EOF` подразумевает, что существует либо` checkLengthOfFile () `, за которым следует` writeAtLocation (endOfFile) `, в то время как`> `просто выполняет` writeAtLocation (CurrentLocation) `. Оба запускают одинаковые проверки на уровне файловой системы, чтобы убедиться, что файл достаточно длинный или требует разреженного расширения, но для проверки длины сначала необходим дополнительный шаг. Я бы ожидал, что при хорошем кэшировании диска разница будет где-то между тривиальной и несуществующей ... Mokubai 5 лет назад 0
@Mokubai, любая здравомыслящая ОС будет иметь длину файла под рукой, когда она открыта, и проверка флага и перемещение смещения в конец просто исчезнет во всех остальных бухгалтериях. Попытка эмулировать `O_APPEND` с помощью` lseek () `перед каждым` write () `будет отличаться, хотя это приведет к дополнительным затратам на системный вызов. (И, конечно, это не сработает, поскольку другой процесс может `write ()` между ними.) ilkkachu 5 лет назад 1
@ilkkachu Я понял тебя. При прочтении вашего другого комментария все это обрабатывается в битах и ​​бобах уровня файловой системы и, по сути, бесплатно благодаря способу открытия дескриптора файла. Я признаю, что мой комментарий был с позиции немного наивного и устарелого знания о том, как вещи * раньше * делались ... Mokubai 5 лет назад 1
@Mokubai Виртуальные файловые системы Unix / Linux обычно реализуют структуру `vnode` для каждого открытого файла. BSD можно увидеть по адресу [https://man.openbsd.org/vnode.9](https://man.openbsd.org/vnode.9). Я не внимательно изучил BSD, но подозреваю, что `void * v_data; / * личные данные для поля fs * / `в структуре содержат соответствующие метаданные, такие как текущая длина файла и текущее смещение файла. Поскольку операции `write ()` включают в себя блокировку структур, выяснить, где записать, - это просто "посмотреть флаги открытия файла, а затем использовать текущую длину или смещение в зависимости от значения флагов". Andrew Henle 5 лет назад 0
Конечно, [logrotate - это не ответ _this_ века] (http://jdebp.info./FGA/do-not-use-logrotate.html). https://superuser.com/a/868519/38062 https://superuser.com/a/291397/38062 https://superuser.com/a/291397/38062 https://unix.stackexchange.com/a / 392924/5132 JdeBP 5 лет назад 1
@AndrewHenle: Я считаю, что вы (хотя бы частично) смотрите на это задом наперед. С одной стороны, я удивлен тем, что размер файла явно не указан в структуре `vnode`; Казалось бы, это атрибут, общий для всех типов файловых систем. Но, возможно, он похоронен под `v_data`. С другой стороны, помните, что у нескольких процессов может быть открыт один и тот же файл одновременно, * с разными смещениями файлов. * Смещение файла не может быть в структуре `inode` /` vnode`, но должно быть в `file` (описание) структура; может быть несколько записей в файле `file` для одного файла. … (Продолжение) Scott 5 лет назад 0
(Продолжение)… Я мог найти только очень старую копию `file.h` [здесь] (https://pages.lip6.fr/Pierre.Sens/srcv6/file.h.html). [Этот раздаточный материал] (http://www.cs.rpi.edu/academics/courses/fall04/os/c18/) показывает взаимосвязь между структурами. [Этот код] (https://minnie.tuhs.org//cgi-bin/utree.pl?file=4BSD/usr/src/sys/sys/sys2.c) из BSD 4 показывает, что `f_offset` копируется из структура `file` для` u.u_offset` и [this] (https://minnie.tuhs.org//cgi-bin/utree.pl?file=4BSD/usr/src/sys/sys/rdwri. c) показывает `` readi`` и `` writei``, используя `u.u_offset` в качестве смещения в файле. … (Продолжение) Scott 5 лет назад 0
(Продолжение)… PS Я не смог найти, где обрабатывается `O_APPEND`. Scott 5 лет назад 0