python: путаница при вводе данных из канала

#python #shell #while-loop #stdin

Вопрос:

Мой скрипт на python можно использовать в двух режимах:

 ./foo arg file
 

или

 cat file | foo arg
 

Вот как я решаю, является ли stdin трубой или нет:

 if sys.stdin.isatty():
    print('not pipen')
else:
    print('is pipe')
 

и он ведет себя так, как и ожидалось:

 ./foo arg file
not pipe

cat file | ./foo arg
is pipe
 

однако при следующем использовании он думает, что ввод поступает из канала, даже если канал является всего лишь частью цикла while:

 while read F ; do foo arg $F ; done < /tmp/zz
is pipe
 

Я называю свой сценарий как ./foo arg file . Почему он думает, что вход-это труба?

Ответ №1:

 while read F ; do foo arg $F ; done < /tmp/zz
 

Вы перенаправляете while стандартный ввод цикла из /tmp/zz (я предполагаю, что это файл). Это включает в себя не только read тест на условие, но и все операторы в теле цикла (если, конечно, они не перенаправлены иным образом).

Ваш код python проверяет только, является ли стандартный ввод tty/терминалом, и предполагает, что в противном случае это канал. Но стандартный ввод может быть из любого типа открытого файлового дескриптора, который можно прочитать; tty, канал, обычный файл, сокет и т.д.

Типичный подход к принятию как имен файлов, так и стандартных входных данных в качестве данных программы заключается в рассмотрении количества аргументов командной строки; если требуется, чтобы имена файлов были, но для них недостаточно аргументов, вместо этого считывайте из стандартных входных данных.


При использовании bash или zsh (и, возможно, других оболочек, расширяющих базовый POSIX sh ), если вы хотите, чтобы программа находилась в цикле, подобном этому, считывалась с исходных стандартных входных данных сценария, один из методов заключается в перенаправлении на другой дескриптор, отличный от 0 (Стандартные входные данные), и вместо этого велите read читать из него:

 while read -r -u 3 f; do foo arg "$f" ; done 3< /tmp/zz
 

который открывает файл /tmp/zz как дескриптор номер 3.

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

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

2. @400theCat наверняка где-то в вашей программе вы определяете, из какого файла вы читаете stdin или из файла. Разве вы не можете где-нибудь сохранить результат этого определения?

Ответ №2:

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

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

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

1. Я не могу использовать количество аргументов в качестве определяющего, является ли вход каналом или нет. Это приведет к хаосу, если пользователь вызовет неправильное количество аргументов.

2. ??? Тогда вам придется полагаться на эвристику: если последний аргумент является допустимым именем файла, примите его как файл; в противном случае прочитайте из STDIN. Тем не менее, все это пахнет для меня как плохо определенный пользовательский интерфейс. Еще одна возможность, которая используется многими программами: возьмите параметр, состоящий из одного тире ( - ), чтобы обозначить, что это не файл, а прочитанный из stdin здесь .