Чому 'ls> ls.out' спричинює включення 'ls.out' до списку імен?


26

Чому $ ls > ls.outпричина "ls.out" буде включена до списку імен файлів у поточному каталозі? Чому саме цього обрали? Чому б інакше?


3
ймовірно, тому, що спочатку створюється файл ls.out, а потім пише вихід до нього
Димитрій Підборський

1
Якщо ви хочете уникнути включення, ви завжди можете зберігати вихідний файл в іншому каталозі. Наприклад, ви можете вибрати батьківський каталог (за умови, що ви не в корені файлової системи) за допомогою ls > ../ls.out
Elder Geek

Відповіді:


36

При оцінці команди >перенаправлення спочатку вирішується: так до моменту 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, програма / сценарій / все, що завгодно, не буде перенаправлено;

  • не заздалегідь перенаправляючи STDOUT, слід обов'язково зробити буфер оболонки вихідним програмою / сценарієм / будь-якими, поки STDOUT не буде перенаправлений;

Отже марна трата часу в першому випадку і марна трата часу і пам’яті у другому випадку.

Це саме те, що трапляється у мене, я не стверджую, що це фактичні причини; але я здогадуюсь, що взагалі, якби у когось був вибір, вони все-таки пішли б з перенаправленням все-таки з вищезазначених причин.


1
Зауважте, що під час перенаправлення оболонка насправді не торкається даних (ні переадресації вводу, ні переадресації виводу). Він просто відкриває файл і передає дескриптор файлу вниз програмі.
Пітер Грін

11

Від 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, але може їх вихід пропадуть в файл , якщо файл не існує , в першу чергу? Це як вода. Чи можете ви отримати склянку води, не поклавши скло під кран спочатку?


10

Я не згоден з нинішніми відповідями. Вихідний файл повинен бути відкритий до запуску команди, або команді не буде де записати свій вихід.

Це тому, що "все є файлом" у нашому світі. Вихід на екран 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

6

Також є приємна стаття про реалізацію перенаправлення та операторів труб в оболонці . Що показує, як переадресація може бути реалізована, щоб це $ 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"); 
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.