Linux pass stdin as file

Using /dev/stdin and a heredoc to pass a file from the command line

I’m curious about the theory behind how heredocs can be passed as a file to a command line utility.

Recently, I discovered I can pass a file as heredoc.

This is advantageous for me for several reasons:

  • Heredocs improve readability for multi line inputs.
  • I don’t need to memorize each utilities flag for passing the file contents from the command line.
  • I can use single and double quotes in the given files.
  • I can control shell expansion.

I’m not clear what is happening. It seems like the shell thinks the heredoc is a file with contents equal to the value of the heredoc. I’ve this technique used with cat, but I’m still not sure what was going on:

I know cat prints the contents of a file, so presumably this heredoc is a temporary file of some kind.

I’m confused about what precisely is going on when I «pass a heredoc to a command line program».

Here’s an example using ansible-playbook. I pass the utility a playbook as a heredoc; however it fails, as shown using echo $? :

However, if I pass the utility the same heredoc but preceed it with /dev/stdin it succeeds

  • What precisly is going on when one «passes a heredoc as a file»?
  • Why does the first version with ansible-playbook fail but second version succeed?
  • What is the significance of passing /dev/stdin before the heredoc?
  • Why do other utilities like ruby or awk not need the /dev/stdin before the heredoc?

3 Answers 3

What precisely is going on when one «passes a heredoc as a file»?

is exactly equivalent to

awk , cat , and ruby all read from standard input if they aren’t given a filename to read from on the command line. That is an implementation choice.

Why does the first version with anisble-playbook fail but second version succeed?

ansible-playbook does not read from standard input by default, but requires a file path instead. This is a design choice.

/dev/stdin is quite likely a symlink to /dev/fd/0 , which is a way of talking about the current process’s file descriptor #0 (standard input). That’s something exposed by your kernel (or system library). The ansible-playbook command opens /dev/stdin like a regular filesystem file and ends up reading its own standard input, which would otherwise have been ignored.

You likely also have /dev/stdout and /dev/stderr links to FDs 1 & 2, which you can use as well if you’re telling something where to put its output.

What is the significance of passing /dev/stdin before the heredoc?

It is an argument to the ansible-playbook command.

Why do other utilities like ruby or awk not need the /dev/stdin before the heredoc?

They read from standard input by default as a design choice, because they are made to be used in pipelines. They write to standard output for the same reason.

What exactly is going on with here-docs is depending on how shell implements here-doc: it may be either done with pipes internally as in case of dash or with temporary file descriptor, as in bash . So in one case it may not be possible to lseek() , but in the other — it can be (which for average user it means you can jump around the contents of the here-doc). See related answer.

As for the case of two ansible-playbook commands, it also depends on how command is implemented (so unless you read source code you won’t actually know). Some commands simply check whether or not there is a file provided, and don’t support stdin . Other commands like awk and ruby — they are designed to expect stdin or a file specified on command-line.

What you can try doing, however, is if you’re using Linux, run strace ansible-playbook . and see what things it tries to open, what syscalls occur, etc. For example, you’ll see that with strace -e open tail /dev/stdin the tail command will actually try to open /dev/stdin as file, whereas trace -e open tail doesn’t.

A here-document is a redirection into the standard input of a command, just like . This means that anywhere where you may use to redirect contents from a file, you may instead redirect the contents of a here-document. The POSIX standard lists here-documents along with the other redirection operators.

In your Ansible example, ansible-playbook does not by default read from its standard input stream as it expects a filename. By giving it /dev/stdin as the filename and then supplying the here-document on standard input, you bypass this restriction in the utility. The /dev/stdin «file» will always contain the standard input data stream of the current process.

ruby and awk as well as many other utilities will read from standard input unless a filename is supplied on the command line.

Читайте также:  Windows still installing update

So, you are technically wrong when you say «It seems like the shell thinks the heredoc is a file with contents equal to the value of the heredoc». It does not act like a file (with regards to having a filename and being seekable), but as a data stream on standard input. At least from the point of view of the utility.

The difference is the same as between

In the first instance, cat opens the file file , but in the second (which is also what happens with a here-document), since no filename was given as an argument to cat , cat just reads its standard input stream (and the shell opens the file, or provides the here-document, on standard input to the utility). The utility does not need to know if the provided data comes from a file, a pipe, a here-document or some other data source.

How here-documents are implemented by the shell is in a way unimportant, but it may be through the use of a FIFO or indeed with a temporary file.

Источник

how to pass a file as stdin in a shell script

I have a bash script which works when called like this: ./stats.sh -rows test_file

The program basically calculates row averages and medians & column averages and medians. Now to the program I want to pass file as standard input. but when I am running this code it prints «you have 2 provide 2 arguments» . what changes should I have to make so that the code takes stdin as a file. I mean to say If I want to run the script I can run it by this way as well ./stats.sh -rows . I want to get this functionality!!

the input file is: (columns separated by tabs)

the code which I worked on is this:

3 Answers 3

You may also redirect a file to stdin within your script:

Use the read a insight your bash script to read/process the content of the stdin.

In this case cat test_file.txt | stats.sh will work.

On many modern architectures, if a script absolutely requires you to pass a file name parameter, you can pass /dev/stdin to have standard input available as a file name-like entity. Many scripts also accept — as a special file name to mean «don’t open a file; read standard input instead». Something like ./stats.sh -rows — might actually work, too. (Of course, that’s a silly example, since ./stats.sh -rows file is equivalent; but it matters for something like stuff | ./stats.sh -rows — .)

However, the script suffers from numerous design flaws, and also has at least one syntax error (you cannot have a space in the assignment FILE= «$<4>» . The fourth argument will simply be evaluated as a command. This might even have security implications, depending on how you run this script). I would seriously consider replacing it entirely with one or two simple Perl or Awk scripts. The shell is just not very ideal for the sort of arithmetic you are tasking it with.

Источник

How to use the stdin as a file

I have a command that wants a file as input, but I get the input from the stdin and I don’t want to create a temporary file.

I would like to do the following:

but that causes an I/O error. The followingworks, but is it possible without using a temporary intermediate file_?

As you might have guessed, I’m on macOS with bash 3.2. However, a zsh 5.3 solution would also be welcomed.

1 Answer 1

With zsh , you can do:

Where =(. ) is a third form of process substitution that uses a temporary files instead of a pipe.

With bash versions prior to 5.1 or zsh , for input that doesn’t end in empty lines (and in bash that doesn’t contain NUL bytes), you can do:

As here-strings (that come from zsh) like here documents (that come from the Bourne shell) are implemented with (deleted) temporary files in both bash¹ and zsh. On macOS (as opposed to Linux), that assumes, cmd opens the file only once (it’s free to seek within it though), as each opening would start at the same position within the file as left at the preceding opening which would likely confuse cmd if it assumes the position is at the start of the file.

¹ note that bash behaviour changed in 5.0 where bash started to go out of its way to make the temporary file non-writable, and in 5.1 where it switched to using pipes instead of temporary files for small ones

Источник

Bash-скрипты, часть 4: ввод и вывод

В прошлый раз, в третьей части этой серии материалов по bash-скриптам, мы говорили о параметрах командной строки и ключах. Наша сегодняшняя тема — ввод, вывод, и всё, что с этим связано.

Вы уже знакомы с двумя методами работы с тем, что выводят сценарии командной строки:

  • Отображение выводимых данных на экране.
  • Перенаправление вывода в файл.

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

Стандартные дескрипторы файлов

Всё в Linux — это файлы, в том числе — ввод и вывод. Операционная система идентифицирует файлы с использованием дескрипторов.

Читайте также:  Забыл пароль для windows live

Каждому процессу позволено иметь до девяти открытых дескрипторов файлов. Оболочка bash резервирует первые три дескриптора с идентификаторами 0, 1 и 2. Вот что они означают.

  • 0 , STDIN — стандартный поток ввода.
  • 1 , STDOUT — стандартный поток вывода.
  • 2 , STDERR — стандартный поток ошибок.

Эти три специальных дескриптора обрабатывают ввод и вывод данных в сценарии.
Вам нужно как следует разобраться в стандартных потоках. Их можно сравнить с фундаментом, на котором строится взаимодействие скриптов с внешним миром. Рассмотрим подробности о них.

STDIN

STDIN — это стандартный поток ввода оболочки. Для терминала стандартный ввод — это клавиатура. Когда в сценариях используют символ перенаправления ввода — , Linux заменяет дескриптор файла стандартного ввода на тот, который указан в команде. Система читает файл и обрабатывает данные так, будто они введены с клавиатуры.

Многие команды bash принимают ввод из STDIN , если в командной строке не указан файл, из которого надо брать данные. Например, это справедливо для команды cat .

Когда вы вводите команду cat в командной строке, не задавая параметров, она принимает ввод из STDIN . После того, как вы вводите очередную строку, cat просто выводит её на экран.

STDOUT

STDOUT — стандартный поток вывода оболочки. По умолчанию это — экран. Большинство bash-команд выводят данные в STDOUT , что приводит к их появлению в консоли. Данные можно перенаправить в файл, присоединяя их к его содержимому, для этого служит команда >> .

Итак, у нас есть некий файл с данными, к которому мы можем добавить другие данные с помощью этой команды:

То, что выведет pwd , будет добавлено к файлу myfile , при этом уже имеющиеся в нём данные никуда не денутся.

Перенаправление вывода команды в файл

Пока всё хорошо, но что если попытаться выполнить что-то вроде показанного ниже, обратившись к несуществующему файлу xfile , задумывая всё это для того, чтобы в файл myfile попало сообщение об ошибке.

После выполнения этой команды мы увидим сообщения об ошибках на экране.

Попытка обращения к несуществующему файлу

При попытке обращения к несуществующему файлу генерируется ошибка, но оболочка не перенаправила сообщения об ошибках в файл, выведя их на экран. Но мы-то хотели, чтобы сообщения об ошибках попали в файл. Что делать? Ответ прост — воспользоваться третьим стандартным дескриптором.

STDERR

STDERR представляет собой стандартный поток ошибок оболочки. По умолчанию этот дескриптор указывает на то же самое, на что указывает STDOUT , именно поэтому при возникновении ошибки мы видим сообщение на экране.

Итак, предположим, что надо перенаправить сообщения об ошибках, скажем, в лог-файл, или куда-нибудь ещё, вместо того, чтобы выводить их на экран.

▍Перенаправление потока ошибок

Как вы уже знаете, дескриптор файла STDERR — 2. Мы можем перенаправить ошибки, разместив этот дескриптор перед командой перенаправления:

Сообщение об ошибке теперь попадёт в файл myfile .

Перенаправление сообщения об ошибке в файл

▍Перенаправление потоков ошибок и вывода

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

Перенаправление ошибок и стандартного вывода

Оболочка перенаправит то, что команда ls обычно отправляет в STDOUT , в файл correctcontent благодаря конструкции 1> . Сообщения об ошибках, которые попали бы в STDERR , оказываются в файле errorcontent из-за команды перенаправления 2> .

Если надо, и STDERR , и STDOUT можно перенаправить в один и тот же файл, воспользовавшись командой &> :

Перенаправление STDERR и STDOUT в один и тот же файл

После выполнения команды то, что предназначено для STDERR и STDOUT , оказывается в файле content .

Перенаправление вывода в скриптах

Существует два метода перенаправления вывода в сценариях командной строки:

  • Временное перенаправление, или перенаправление вывода одной строки.
  • Постоянное перенаправление, или перенаправление всего вывода в скрипте либо в какой-то его части.

▍Временное перенаправление вывода

В скрипте можно перенаправить вывод отдельной строки в STDERR . Для того, чтобы это сделать, достаточно использовать команду перенаправления, указав дескриптор STDERR , при этом перед номером дескриптора надо поставить символ амперсанда ( & ):

Если запустить скрипт, обе строки попадут на экран, так как, как вы уже знаете, по умолчанию ошибки выводятся туда же, куда и обычные данные.

Запустим скрипт так, чтобы вывод STDERR попадал в файл.

Как видно, теперь обычный вывод делается в консоль, а сообщения об ошибках попадают в файл.

Сообщения об ошибках записываются в файл

▍Постоянное перенаправление вывода

Если в скрипте нужно перенаправлять много выводимых на экран данных, добавлять соответствующую команду к каждому вызову echo неудобно. Вместо этого можно задать перенаправление вывода в определённый дескриптор на время выполнения скрипта, воспользовавшись командой exec :

Перенаправление всего вывода в файл

Если просмотреть файл, указанный в команде перенаправления вывода, окажется, что всё, что выводилось командами echo , попало в этот файл.

Команду exec можно использовать не только в начале скрипта, но и в других местах:

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

Перенаправление вывода в разные файлы

Сначала команда exec задаёт перенаправление вывода из STDERR в файл myerror . Затем вывод нескольких команд echo отправляется в STDOUT и выводится на экран. После этого команда exec задаёт отправку того, что попадает в STDOUT , в файл myfile , и, наконец, мы пользуемся командой перенаправления в STDERR в команде echo , что приводит к записи соответствующей строки в файл myerror.

Читайте также:  Драйвера wifi для windows 10 dell inspiron

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

Перенаправление ввода в скриптах

Для перенаправления ввода можно воспользоваться той же методикой, которую мы применяли для перенаправления вывода. Например, команда exec позволяет сделать источником данных для STDIN какой-нибудь файл:

Эта команда указывает оболочке на то, что источником вводимых данных должен стать файл myfile , а не обычный STDIN . Посмотрим на перенаправление ввода в действии:

Вот что появится на экране после запуска скрипта.

В одном из предыдущих материалов вы узнали о том, как использовать команду read для чтения данных, вводимых пользователем с клавиатуры. Если перенаправить ввод, сделав источником данных файл, то команда read , при попытке прочитать данные из STDIN , будет читать их из файла, а не с клавиатуры.

Некоторые администраторы Linux используют этот подход для чтения и последующей обработки лог-файлов.

Создание собственного перенаправления вывода

Перенаправляя ввод и вывод в сценариях, вы не ограничены тремя стандартными дескрипторами файлов. Как уже говорилось, можно иметь до девяти открытых дескрипторов. Остальные шесть, с номерами от 3 до 8, можно использовать для перенаправления ввода или вывода. Любой из них можно назначить файлу и использовать в коде скрипта.

Назначить дескриптор для вывода данных можно, используя команду exec :

После запуска скрипта часть вывода попадёт на экран, часть — в файл с дескриптором 3 .

Перенаправление вывода, используя собственный дескриптор

Создание дескрипторов файлов для ввода данных

Перенаправить ввод в скрипте можно точно так же, как и вывод. Сохраните STDIN в другом дескрипторе, прежде чем перенаправлять ввод данных.

После окончания чтения файла можно восстановить STDIN и пользоваться им как обычно:

В этом примере дескриптор файла 6 использовался для хранения ссылки на STDIN . Затем было сделано перенаправление ввода, источником данных для STDIN стал файл. После этого входные данные для команды read поступали из перенаправленного STDIN , то есть из файла.

После чтения файла мы возвращаем STDIN в исходное состояние, перенаправляя его в дескриптор 6 . Теперь, для того, чтобы проверить, что всё работает правильно, скрипт задаёт пользователю вопрос, ожидает ввода с клавиатуры и обрабатывает то, что введено.

Закрытие дескрипторов файлов

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

После исполнения скрипта мы получим сообщение об ошибке.

Попытка обращения к закрытому дескриптору файла

Всё дело в том, что мы попытались обратиться к несуществующему дескриптору.

Будьте внимательны, закрывая дескрипторы файлов в сценариях. Если вы отправляли данные в файл, потом закрыли дескриптор, потом — открыли снова, оболочка заменит существующий файл новым. То есть всё то, что было записано в этот файл ранее, будет утеряно.

Получение сведений об открытых дескрипторах

Для того, чтобы получить список всех открытых в Linux дескрипторов, можно воспользоваться командой lsof . Во многих дистрибутивах, вроде Fedora, утилита lsof находится в /usr/sbin . Эта команда весьма полезна, так как она выводит сведения о каждом дескрипторе, открытом в системе. Сюда входит и то, что открыли процессы, выполняемые в фоне, и то, что открыто пользователями, вошедшими в систему.

У этой команды есть множество ключей, рассмотрим самые важные.

  • -p Позволяет указать ID процесса.
  • -d Позволяет указать номер дескриптора, о котором надо получить сведения.

Для того, чтобы узнать PID текущего процесса, можно использовать специальную переменную окружения $$ , в которую оболочка записывает текущий PID .

Ключ -a используется для выполнения операции логического И над результатами, возвращёнными благодаря использованию двух других ключей:

Вывод сведений об открытых дескрипторах

Тип файлов, связанных с STDIN , STDOUT и STDERR — CHR (character mode, символьный режим). Так как все они указывают на терминал, имя файла соответствует имени устройства, назначенного терминалу. Все три стандартных файла доступны и для чтения, и для записи.

Посмотрим на вызов команды lsof из скрипта, в котором открыты, в дополнение к стандартным, другие дескрипторы:

Вот что получится, если этот скрипт запустить.

Просмотр дескрипторов файлов, открытых скриптом

Скрипт открыл два дескриптора для вывода ( 3 и 6 ) и один — для ввода ( 7 ). Тут же показаны и пути к файлам, использованных для настройки дескрипторов.

Подавление вывода

Иногда надо сделать так, чтобы команды в скрипте, который, например, может исполняться как фоновый процесс, ничего не выводили на экран. Для этого можно перенаправить вывод в /dev/null . Это — что-то вроде «чёрной дыры».

Вот, например, как подавить вывод сообщений об ошибках:

Тот же подход используется, если, например, надо очистить файл, не удаляя его:

Итоги

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

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

Уважаемые читатели! В этом материале даны основы работы с потоками ввода, вывода и ошибок. Уверены, среди вас есть профессионалы, которые могут рассказать обо всём этом то, что приходит лишь с опытом. Если так — передаём слово вам.

Источник

Оцените статью