В недавно опубликованной статье «Три наблюдения о командной строке и путях в файловой системе» были рассмотрены некоторые особенности интерпретации командной строки оболочками в операционных системах Windows и Linux. Первое наблюдение было о том, что командные оболочки SH/BASH, в отличие от COMMAND/CMD, выполняют предварительную обработку параметров, содержащих шаблоны имён файлов. Перед выполнением команды параметр с шаблоном заменяется оболочкой на список параметров с именами файлов, которым этот шаблон удовлетворяет. То есть, команда:

$ cat book/chapter*.txt

перед выполнением будет преобразована командной оболочкой в команду

$ cat book/chapter1.txt book/chapter2.txt book/chapter3.txt

если в каталоге book имеются файлы chapter1.txt, chapter2.txt и chapter3.txt.

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

Допустим, в текущем каталоге ведётся разработка программы на языке Си, которая содержит заголовочный файл mysockets.h к собственной библиотеке сетевых интерфейсов mysockets.c. Тут же редактируется main.c, тут же выполняется сборка проекта командой make. В какой-то момент возникло желание посмотреть, а какие стандартные заголовочные файлы на тему сетевых сокетов установлены в системе? Для этого «не отходя от кассы» набирается такая команда:

$ find /usr/include -name *sock*.h

А в ответ — тишина. Может быть, никаких таких файлов и не установлено? Но терзают всё-таки сомнения: как так, чтобы в Linux и не было заголовочных файлов, содержащих sock в своём имени?! Проверяем:

$ ls /usr/include/linux/*sock*.h

Получаем:

/usr/include/linux/sock_diag.h
/usr/include/linux/socket.h
/usr/include/linux/sockios.h
/usr/include/linux/vm_sockets.h

Что за мистика?! Пробуем по-другому:

$ find /usr/include/linux -name *sock*.h

Опять тишина. Прямо иллюзион какой-то. Неужели программа find сломалась?

Не будем нагнетать атмосферу и выдвигать множество гипотез для объяснения наблюдаемого явления. На самом деле всё просто. И причина — в той самой предварительной обработке параметров командной оболочкой. Перед тем, как запустить программу find, командная оболочка, встретив параметр *sock*.h, содержащий подстановочные символы, проверила содержимое текущего каталога, обнаружила в нём удовлетворяющий шаблону файл mysockets.h и заботливо заменила значение параметра именем найденного файла. После этого она выполнила уже такую команду:

$ find /usr/include/linux -name mysockets.h

В дереве файловой системы /usr/include/linux файла с именем mysockets.h не нашлось, поэтому вывод команды find оказался пустым.

Как всё же добиться желаемого? Чтобы запретить командной оболочке выполнять обработку параметра, надо заключить его в одинарные кавычки (апострофы):

$ find /usr/include/linux -name '*sock*.h'

Этот вариант отработает в соответствии с первоначальной задумкой. А можно использовать вместо одинарных кавычек двойные? Можно, и в данном случае результат тоже будет положительным. Только надо иметь в виду, что содержимое двойных кавычек обрабатывается командной оболочкой на предмет подстановки значений переменных окружения. Можно выполнить, например, следующие две команды, и сравнить их вывод:

$ echo '$SHELL'
$SHELL
$ echo "$SHELL"
/bin/bash

Но это уже совсем другая история...

Комментарии (12)


  1. saboteur_kiev
    11.01.2024 17:37
    +11

    Не, вы серьезно?

    То, что если вы не хотите раскрывать маски, нужно брать в кавычки, рассмотрено уже в инете во всех углах, включая в комментах в прошлой статье. Зачем ради этого еще целую статью делать?

    Давайте я еще сразу расскажу, что поиск всех файлов в линукс - это *, а в дос это *.* (потому что имя и расширение это в fat16 разные поля directory entry, и Windows как наследница DOS до сих пор поддерживает это для обратной совместимости.

    Но это все вытекает из общего правила как идет разрешение wildcard в командной строке Linux, и правил кавычек. Пожалуйста, горшочек не вари.


    1. R0bur Автор
      11.01.2024 17:37
      -2

      Давайте я еще сразу расскажу, что поиск всех файлов в линукс - это *, а в дос это . (потому что имя и расширение это в fat16 разные поля directory entry, и Windows как наследница DOS до сих пор поддерживает это для обратной совместимости.

      А можно подробнее? Только что выполнил в Windows команду " DIR * " - отобразились все файлы. Сделал то же самое в эмуляторе DosBox - тоже отобразились. Или Вы имеете в виду, что надо именно в 16-разрядном MS DOS выполнять? Но ведь MS DOS мы не обсуждаем?


      1. saboteur_kiev
        11.01.2024 17:37
        +3

        Подробнее - у вас в windows * и *.* работает примерно одинаково.
        А в линукс *.* - найти имена, в которых есть хотя бы одна точка.


    1. R0bur Автор
      11.01.2024 17:37
      +1

      А, кажется понял. Вы имеете в виду, что в Windows маска *.* удовлетворяет всем файлам, тогда как в Linux - только тем, которые содержат в имени "точку". Ну да, ну да.


    1. omgiafs
      11.01.2024 17:37
      +2

      Я буду ещё категоричнее. Это прямо в мануале bash описано.

      Недавно поймал себя на мысли, что 95% всех околоайтишных статей - это художественные пересказы отдельных абзацев мануалов различного ПО.

      Не такого "распространения знаний" хотели создатели Интернета.


    1. APh
      11.01.2024 17:37

      Ну, в NTFS расширений уже нет. А суффикс "точка, три символа" --- дань привычке, удобству, совместимости. Просто звёздочка должна в Уиндоуз все файлы подтащить, не?


      1. saboteur_kiev
        11.01.2024 17:37

        А если я хочу подтащить файлы в которых есть точка?


        1. R0bur Автор
          11.01.2024 17:37
          +1

          Если хотите, чтобы маска "*.*" обрабатывалась как в SH/BASH, то в Windows воспользуйтесь оболочкой PowerShell.


  1. Batalmv
    11.01.2024 17:37
    +2

    Я вам еще подкину :)

    /home/mobaxterm/tt> ll
    total 0
    -rw-r--r-- 1 mbatal UsersGrp 0 Jan 11 18:39 111
    -rw-r--r-- 1 mbatal UsersGrp 0 Jan 11 18:39 222

    /home/mobaxterm/tt> for i in '*'; do echo $i; done
    111 222

    /home/mobaxterm/tt> for i in *; do echo $i; done
    111
    222

    -----------------

    Я думаю вдумчивое чтение man bash (1): GNU Bourne-Again SHell (manpages.org) вам откроет еще столько всего :)


    1. saboteur_kiev
      11.01.2024 17:37
      +2

      давайте еще накинем

      $ touch 1 2 .1 .
      $ ls *
      1  2
      $ ls .*
      .1 .2
      $ ls -A.1 .2 1 2


      1. Batalmv
        11.01.2024 17:37

        Это автор в прошлой статье затронул

        А вот тут поинтереснее разыменование :) Вроде и в кавычках, но нет - разыменовывает :)

        Это я к тому, что выдирать куски из доки не очень хорошо, а так вульгарно - и вообще крайне вредно для тех, кто будет читать :)


  1. 0Bannon
    11.01.2024 17:37

    Спасибо. Интересно было прочитать.