Почему $ ls > ls.out
ls.out включается в список имен файлов в текущем каталоге? Почему это было выбрано? Почему не иначе?
ls > ../ls.out
Почему $ ls > ls.out
ls.out включается в список имен файлов в текущем каталоге? Почему это было выбрано? Почему не иначе?
ls > ../ls.out
Ответы:
При оценке команды >
перенаправление сначала разрешается: поэтому к тому времени, как ls
запускается, выходной файл уже создан.
Это также причина, по которой чтение и запись в один и тот же файл с помощью >
перенаправления в пределах одной и той же команды усекает файл; к моменту запуска команды файл уже обрезан:
$ echo foo >bar
$ cat bar
foo
$ <bar cat >bar
$ cat bar
$
Уловки, чтобы избежать этого:
<<<"$(ls)" > ls.out
(работает для любой команды, которая должна быть запущена до разрешения перенаправления)
Подстановка команд выполняется до оценки внешней команды, поэтому ls
выполняется до ls.out
создания:
$ ls
bar foo
$ <<<"$(ls)" > ls.out
$ cat ls.out
bar
foo
ls | sponge ls.out
(работает для любой команды, которая должна быть запущена до разрешения перенаправления)
sponge
записывает в файл только после завершения остальной части канала, поэтому ls
выполняется до ls.out
создания ( sponge
предоставляется с moreutils
пакетом):
$ ls
bar foo
$ ls | sponge ls.out
$ cat ls.out
bar
foo
ls * > ls.out
(работает для ls > ls.out
конкретного случая)
Расширение имени файла выполняется до разрешения перенаправления, поэтому ls
будет работать с его аргументами, которые не будут содержать ls.out
:
$ ls
bar foo
$ ls * > ls.out
$ cat ls.out
bar
foo
$
О том, почему переназначения разрешаются до программы / скрипта / все , что бежать, я не вижу причин , почему конкретные это обязательное сделать это, но я вижу две причины , почему это лучше сделать так:
если вы не перенаправите STDIN заранее, программа / скрипт / все остальное будет удерживаться до перенаправления STDIN;
предварительное перенаправление STDOUT не обязательно должно делать буфер оболочки обязательным для вывода программы / скрипта / что угодно, пока не будет перенаправлен STDOUT;
Таким образом, пустая трата времени в первом случае и пустая трата времени и памяти во втором случае.
Это то, что происходит со мной, я не утверждаю, что это реальные причины; но я думаю, что в целом, если бы у кого-то был выбор, они все равно пошли бы с перенаправлением раньше по вышеуказанным причинам.
От man bash
:
Переквалификация
Перед выполнением команды ее ввод и вывод могут быть перенаправлены с использованием специальных обозначений, интерпретируемых оболочкой. Перенаправление позволяет дублировать, открывать, закрывать дескрипторы файлов команд, чтобы они ссылались на разные файлы, и может изменять файлы, из которых команда читает и записывает.
Первое предложение предполагает, что вывод сделан для того, чтобы идти куда-то, кроме stdin
перенаправления, непосредственно перед выполнением команды. Таким образом, чтобы быть перенаправленным в файл, файл сначала должен быть создан самой оболочкой.
Чтобы избежать файла, я предлагаю вам перенаправить вывод сначала в именованный канал, а затем в файл. Обратите внимание на использование &
для возврата контроля над терминалом пользователю
DIR:/xieerqi
skolodya@ubuntu:$ mkfifo /tmp/namedPipe.fifo
DIR:/xieerqi
skolodya@ubuntu:$ ls > /tmp/namedPipe.fifo &
[1] 14167
DIR:/xieerqi
skolodya@ubuntu:$ cat /tmp/namedPipe.fifo > ls.out
Но почему?
Подумайте об этом - где будет выход? Программа имеет такие функции , как printf
, sprintf
, puts
, которые все по умолчанию идти к stdout
, но может их выход пропадут в файл , если файл не существует , в первую очередь? Это как вода. Можете ли вы получить стакан воды, не ставя стакан под кран сначала?
Я не согласен с текущими ответами. Выходной файл должен быть открыт перед выполнением команды, иначе команде некуда будет записывать свой вывод.
Это потому, что "все это файл" в нашем мире. Вывод на экран - SDOUT (он же дескриптор файла 1). Для приложения, чтобы записать в терминал, он открывает fd1 и пишет в него как файл.
Когда вы перенаправляете вывод приложения в оболочку, вы изменяете fd1, чтобы он фактически указывал на файл. Когда вы передаете по каналу, вы изменяете STDOUT одного приложения, чтобы оно стало STDIN другого (fd0).
Но все это приятно говорить, но вы можете легко посмотреть, как это работает strace
. Это довольно тяжелый материал, но этот пример довольно короткий.
strace sh -c "ls > ls.out" 2> strace.out
Внутри strace.out
мы можем увидеть следующие основные моменты:
open("ls.out", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 3
Это открывается ls.out
как fd3
. Пиши только. Усекает (перезаписывает), если существует, в противном случае создает.
fcntl(1, F_DUPFD, 10) = 10
close(1) = 0
fcntl(10, F_SETFD, FD_CLOEXEC) = 0
dup2(3, 1) = 1
close(3) = 0
Это немного жонглирования. Мы отключаем STDOUT (fd1) от fd10 и закрываем его. Это потому, что мы не выводим что-либо в реальный STDOUT с помощью этой команды. Он заканчивается дублированием дескриптора записи ls.out
и закрытием исходного.
stat("/opt/wine-staging/bin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/home/oli/bin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/usr/local/sbin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/usr/local/bin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/usr/sbin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/usr/bin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/sbin/ls", 0x7ffc6bf028c0) = -1 ENOENT (No such file or directory)
stat("/bin/ls", {st_mode=S_IFREG|0755, st_size=110080, ...}) = 0
Это он ищет исполняемый файл. Урок, возможно, чтобы не иметь длинный путь;)
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f0961324a10) = 31933
wait4(-1, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 31933
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=31933, si_status=0, si_utime=0, si_stime=0} ---
rt_sigreturn() = 31933
dup2(10, 1) = 1
close(10) = 0
Затем команда выполняется, и родитель ждет. Во время этой операции любой STDOUT будет фактически сопоставлен с дескриптором открытого файла ls.out
. Когда дочерний процесс выдает сообщение SIGCHLD
, он сообщает родительскому процессу, что он завершен, и может возобновить его. Это заканчивается немного больше жонглирования и закрытия ls.out
.
Почему так много жонглирования? Нет, я тоже не совсем уверен.
Конечно, вы можете изменить это поведение. Вы можете буферизовать в памяти что-то вроде этого, sponge
и это будет невидимым из исходящей команды. Мы по-прежнему влияем на файловые дескрипторы, но не видимыми для файловой системы.
ls | sponge ls.out
Также есть хорошая статья о реализации перенаправления и конвейерных операторов в оболочке . Который показывает, как перенаправление может быть реализовано так, чтобы $ ls > ls.out
могло выглядеть так:
main(){
close(1); // Release fd no - 1
open("ls.out", "w"); // Open a file with fd no = 1
// Child process
if (fork() == 0) {
exec("ls");
}
}