3> & 1 означает 4> & 3 5> & 3 и т. Д.? - PullRequest
26 голосов
/ 02 ноября 2019

Я бы ожидал, что

echo foo | tee /proc/self/fd/{3..6} 3>&1

завершится ошибкой с ошибками типа / proc / self / fd / 4: такого файла или каталога нет и т. Д., Но, к моему удивлению, этовыводит

foo
foo
foo
foo
foo

Это похоже на 3>&1, когда все последующие дескрипторы перенаправляются на стандартный вывод, за исключением того, что он не работает, если я изменяю 3 на что-то другое, например

$ echo foo | tee /proc/self/fd/{3..6} 4>&1
tee: /proc/self/fd/3: No such file or directory
tee: /proc/self/fd/5: No such file or directory
tee: /proc/self/fd/6: No such file or directory
foo
foo
$ echo foo | tee /proc/self/fd/{4..6} 4>&1
tee: /proc/self/fd/5: No such file or directory
tee: /proc/self/fd/6: No such file or directory
foo
foo

Есть ли объяснение этому поведению?

Ответы [ 2 ]

28 голосов
/ 02 ноября 2019

strace показывает эту последовательность системных вызовов:

$ strace -o strace.log tee /proc/self/fd/{3..6} 3>&1
...
$ cat strace.log
...
openat(AT_FDCWD, "/proc/self/fd/3", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 4
openat(AT_FDCWD, "/proc/self/fd/4", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 5
openat(AT_FDCWD, "/proc/self/fd/5", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 6
openat(AT_FDCWD, "/proc/self/fd/6", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 7
...

Первая строка открывает /proc/self/fd/3 и присваивает ей следующий доступный номер FD, 4. /proc/self/fd/3 - это специальный путь. Открытие его имеет эффект, подобный dup ing fd 3: fd 4 указывает на то же место, что и fd 3, tty.

То же самое происходит для каждого последующего вызова openat(). Когда пыль оседает, все 4, 5, 6 и 7 являются дубликатами 3. 3. 1012 *

  • 1 → tty
  • 3 → tty
  • 4 → tty
  • 5 → tty
  • 6 → tty
  • 7 → tty

Обратите внимание, что перенаправление 3>&1 не важно. Важно то, что мы просим тройник открыть /proc/self/fd/N, где N уже используется. Мы должны получить тот же результат, если избавимся от 3>&1 и получим тройниквместо этого начните с /proc/self/fd/2. Давайте посмотрим:

$ echo foo | tee /proc/self/fd/{2..6}
foo
foo
foo
foo
foo
foo

Подтверждено! Тот же результат.

Мы можем повторять одно и то же число снова и снова. Мы получаем тот же результат, когда нажимаем fd 6. К тому времени, когда он достигает последнего, он открыл достаточно дескрипторов, чтобы сделать возможным переход к 6.

$ echo foo | tee /proc/self/fd/{2,2,2,2,6}
foo
foo
foo
foo
foo
foo
0 голосов
/ 09 ноября 2019

A tee ошибка?

Ответ Джона Кугельмана в порядке, но поскольку проблема сложная, я пойду немного дальше:

bash -c 'exec 2> >(exec sed -ue "s/^/StdErr: /");
    exec 1> >(exec sed -ue "s/^/StdOut: /");
    tee /dev/fd/{2..6} <<<foo'
StdErr: foo
StdOut: foo
StdErr: foo
StdErr: foo
StdErr: foo
StdErr: foo

Показвход (foo) умножается 5x на *STDERR* AND 1x на STDOUT. Таким образом, все /dev/fd/{3..6} связаны с /dev/fd/2.

strace -o >(grep dev/fd) -e openat tee /dev/fd/{2..6} <<<foo 4>/dev/null
foo
foo
foo
foo
openat(AT_FDCWD, "/dev/fd/2", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
openat(AT_FDCWD, "/dev/fd/3", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 5
openat(AT_FDCWD, "/dev/fd/4", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 6
openat(AT_FDCWD, "/dev/fd/5", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 7
openat(AT_FDCWD, "/dev/fd/6", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 8

Таким образом, tee производит 4-кратный ввод на tty вместо 5 (2..6 + STDOUT = 6 ,- 1x / dev / null => 5 посещено foo ):

  1. Доступ /dev/fd/2, открытие дескриптор файла 3 . .. и так создать /dev/fd/3, затем
  2. Доступ /dev/fd/3, который существует сейчас, из предыдущей операции, открытие дескриптор файла 5 (потому что /dev/fd/4привязывается к /dev/null из командной строки ... и так создает /dev/fd/5, затем
  3. Access /dev/fd/4 (привязывается к /dev/null с помощью командной строки) и создать /dev/fd/6, затем
  4. Доступ /dev/fd/5, который существует в настоящее время (привязан к 3, привязан к 2 ...), затем
  5. Доступ /dev/fd/6, которыйсуществует, но привязано к /dev/fd/4, к которому привязано /dev/null.

Ожидаемый результат может быть:

tee /dev/fd/{2..6} <<<foo
foo
tee: /dev/fd/3: No such file or directory
tee: /dev/fd/4: No such file or directory
tee: /dev/fd/5: No such file or directory
tee: /dev/fd/6: No such file or directory
foo

С выходом 1 раз в STDERR и 1 раз вSTDOUT и 4 строки ошибок.

Так что tee не проверяет существование целей перед началом. Но это ошибка? !?

Поскольку открытие файла в write mode может быть в порядке, даже если файл не существует (создание нового файла).

Чтение Время-of-check-time-of-use (TOCTOU) Состояние гонки указано Комментарий Джона Кугельмана поможет понять, почему предварительная проверка неправильная хорошая идея .

Так что, если ошибка, я думаю, ссылка /dev/fd/ непосредственно на tee это ошибка здесь (в командной строке). ... Не ошибка tee, просто пользователь с ошибками; -)

...