Почему $ ls > ls.out вызывает включение «ls.out» в список имен файлов в текущем каталоге? Почему это было выбрано? Почему не иначе?
Из man bash:
REDIRECTION Перед выполнением команды его ввод и вывод могут быть перенаправлены с использованием специальной нотации, интерпретируемой оболочкой. Перенаправление позволяет дублировать, открывать, закрывать файлы команд, ссылаться на разные файлы и изменять файлы, с которых команда считывает и записывает.Первое предложение предполагает, что вывод выполняется для перехода куда-то, кроме 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, чтобы он фактически указывал на файл.
Но вам все это приятно сказать, но вы можете легко посмотреть, как это работает с 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, это говорит о завершении родительского процесса и возобновлении его.
Почему открывает много жонглирования? Нет, я тоже не совсем уверен.
Конечно, вы можете изменить это поведение. Вы можете буферизировать в память с чем-то вроде 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");
}
}