Как именно работает конвейеризация/перенаправление в Linux?

Как именно работает конвейеризация/перенаправление в Linux?

У меня есть три примера перенаправления stdin/stdout, только один из них работает так, как задумано. Я был бы рад, если бы кто-нибудь мог мне это объяснить.

Цель — отсортировать содержимое в файле file1 и сохранить изменения в том же файле.

  1. sort file1 | tee file1 > /dev/null --------> Это работает

  2. sort file1 | tee file1 --------> Содержимое файла file1 будет удалено

  3. сортировать файл1 | tee файл1 > файл2 --------> Содержимое файла1 будет удалено

PS. tee копирует стандартный ввод в каждый ФАЙЛ, а также в стандартный вывод.

Что делает первый пример работающим?

решение1

Согласно моим тестам на Debian Wheezy, все 3 сценария могут привести к обоим результатам (файл file1 сортируется и записывается обратно в себя же, ИЛИ ничего не сортируется и ничего не записывается в file1).

Я считаю, что это нормальное поведение, и оно исходит из того, как Linux работает с файлами. Подумайте о команде — команда sort начинает читать file1 и немедленно отправляет свой вывод в tee. Tee считывает вывод, записывает его обратно в file1 и печатает в /dev/null. В случае, если sort достаточно быстр, чтобы прочитать весь file1, tee получает отсортированный вывод. Но в случае, если tee получает свою блокировку на файле, он стирает его (tee всегда стирает выходной файл, за исключением случаев, когда используется опция append), и это примерно то, что происходит во всех ваших 3 сценариях.

Чтобы сделать это короче, скажем, что иногда sort недостаточно быстр, чтобы прочитать file1. В таком случае tee стирает файл ДО того, как sort сможет его прочитать.

Я бы рекомендовал следующую процедуру:

cat file1 | sort > /tmp/sorting.tmp; mv /tmp/sorting.tmp file1

Если вы хотите увидеть отсортированный вывод на stdout, сделайте это следующим образом:

cat file1 | sort | tee /tmp/sorting.tmp; mv /tmp/sorting.tmp file1

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

решение2

Я сомневаюсь, что такое поведение предсказуемо (и, конечно, не будет зависеть от него). Команда, teeвероятно, запускает новый процесс для отправки своего ввода в «другое» место назначения. Операционная система будет «буферизировать» вывод, пока не достигнет точки, где она создаст файл назначения и запишет свой временный буфер в файл. Точный момент, когда это произойдет (и перезапишет источник), вероятно, зависит от:

  • Размер файла и доступная память для буфера
  • Прошедшее время
  • Если вход из трубы teeзаканчивается

Это глубже, чем bash: Это то, как работают программы, которые bashначинаются. Оболочка просто интерпретирует команды, которые вы вводите, и запускает программы, необходимые для выполнения команд. Оболочка не контролирует, как работает каждая программа, и еще меньше — как эти программы взаимодействуют. Пользователь несет ответственность за то, чтобы попросить программу (или набор программ) взять данные из входного файла и записать результат в тот же входной файл в том же предложении.

Не забывайте, что bash — это всего лишь интерпретатор пользовательских команд: это всего лишь shellинструмент операционной системы для преобразования намерений пользователя в системные вызовы.

И егозадокументировано, тоже! Илиэта почта, который решает похожие проблемы. Или этоТема StackOverflow. Илиэта тема Serverfault.

Обратите внимание, что это также может произойти с перенаправлением stdin: если вы берете входные команды из файла: $ myprog < commandfile. Если myprogзаписывает в commandfile, нет гарантии, что все commandfileкоманды будут выполнены.

По-настоящему простой аналогией будет что-то вроде этого списка инструкций:

- Execute the instructions step by step
- Dip this instruction list in a bucket of black paint
- Type in the following commands:
  find /etc -type f -exec cat '{}' \; | tr -c '.[:digit:]' '\n' \
  | grep '^[^.][^.]*\.[^.][^.]*\.[^.][^.]*\.[^.][^.]*$'

Я полагаю, вы сначала сделаете копию? (команда взята изРасширенное руководство по написанию сценариев Bash)

решение3

То есть вы хотите сохранить исходное содержимое файла, добавив в него изменения?

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

решение4

sort file1 | tee file1 > tmp && mv file1 original && mv tmp file1

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

Связанный контент