скриншот консоли, который рвет шаблон

На картинке выше вы можете наблюдать, как ls считает, что linkylink/.. это не то же самое, что текущий каталог. При этом cd, кажется, с ним не согласен.

Начну рассказ со всем знакомых веб-адресов, которые похожи на системные пути.

Две точки в путях URI (в вебе)


Интерпретация точек описана в секции 5.2.4 RFC 3986.
Работает это так: каждый сегмент из двух точек уничтожает предыдущий сегмент:

/a/b/c/../../g <=> /a/g

при этом, если уничтожать нечего, две точки игнорируются:

example.com/../../../etc/passwd <=> example.com/etc/passwd

Правила были придуманы, чтобы относительные пути (../img/pic.png) можно было преобразовывать в абсолютные префиксом из uri-контекста:

  1. /a/css/index.css ссылается на ../img/pic.png
  2. в /a/css/index.css уничтожается все после последнего слеша => /a/css/
  3. ../img/pic.png прибавляется к /a/css/ => /a/css/../img/pic.png
  4. точки интерпретируются => /a/img/pic.png

Эти операции обычно делаются браузером, когда он преобразовывает относительные uri в абсолютные. Также, согласно стандарту, операция уничтожения точек должна выполняться при приведении uri к каноничному виду — нормализации — в том числе абсолютных uri.

Веб-сервера в дикой природе не сталкиваются с запросами содержащими точки и каждый обрабатывает их по-своему.

В целом, исходя из правила нормализации следует, что в uri вида 'http://example.com/a/b/../c' 'b' не обязана существовать.

Две точки в шелле


Схожим образом себя ведет шелловая команда cd: две точки всегда ведут на один сегмент путя выше, как бы отменяя предыдущий переход в подкаталог. Но, в отличие от uri, шелл проверяет существование промежуточных каталогов.

Если вы считаете первое естественным, то эта публикация для вас. На самом деле шелл эмулирует такое поведение cd: во всех остальных местах *nix ".." работает по-другому.


Даже встроенная команда source (или ее синоним ".") имеет отличное поведение от cd

Разница проявляется на символьных ссылках на каталоги: для cd переход по такой ссылке обратим через "..", тогда как остальная система будет воспринимать ".." как физического родителя каталога, на который ссылка указывает.

По-другому первое поведение называется logical (-L у pwd и cd), в противоположность physical (-P).

Две точки в файловой системе


В *nix ".." — это реальный подкаталог, единственный физический родитель, независимый от символьных ссылок. Если посмотреть внутрь файловой системы ext2, то описание подкаталогов ".." и "." ничем не будет отличаться от других, кроме того, что они перечисляются вначале.

В некоторых случаях (например, в FreeBSD) этот факт даже используется для вычисления пути к рабочему каталогу (который там отдельно не хранится), через последовательные переходы по "..".

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

Нетрудно заметить, что если мы попали в некоторый каталог по символьной ссылке, то у нас не останется информации, чтобы вернуться: ".." будет указывать в физического родителя. Но как тогда cd работает с логическими путями, включающими в себя симлинки? Для этого шелл запоминает путь, по которому он пришел в каталог. Логический путь, построенный шеллом, доступен через переменную окружения $PWD или через pwd [-L].


Две точки требуют внимания, особенно в скриптах.

PS: Символьных ссылок на каталоги очень много в sysfs:

bug@earth /sys/class/net/lo % ls
addr_assign_type  flags              phys_port_id
address           gro_flush_timeout  phys_switch_id
addr_len          ifalias            power
...
bug@earth /sys/class/net/lo % ls ..
lo
bug@earth /sys/class/net/lo % pwd -P; pwd -L
/sys/devices/virtual/net/lo
/sys/class/net/lo
bug@earth /sys/class/net/lo % cd ..; ls
eth0  lo

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


  1. dpivovarov
    29.01.2016 13:16

    Точки в MySQL :)


  1. schuykov
    29.01.2016 13:45
    +5

    Хмм…
    1. «Вообще, в *nix нигде не хранится путь до рабочего каталога процесса, а вместо этого для каждого процесса система запоминает inode (идентификатор файла) текущего и корневого каталогов. Когда где-нибудь требуется путь, он вычисляется последовательными переходами вверх по файловой иерархии, от текущего каталога, до корня, запоминая каждый шаг.»
    Linux конечно не unix, однако в /proc/idПроцесса путь хранится в виде симлинка exec

    Кроме того у Вас не указано, что же такое linkylink?


    1. ForeverYoung
      29.01.2016 14:05

      Надо было его назвать symlink, статья стала бы понятней…


    1. TheRipper
      29.01.2016 14:07

      Если вы про /proc/PID/exe, то он хранится как inode каталога + inode файла, в этом можно убедиться вот так:

      bug@earth ~ % echo $$
      1459
      bug@earth ~ % ls -l /proc/1459/exe
      lrwxrwxrwx 1 bug bug 0 Jan 29 01:37 /proc/1459/exe -> /bin/zsh5
      bug@earth ~ % sudo mv /bin/zsh5 foo
      bug@earth ~ % ls -l /proc/1459/exe
      lrwxrwxrwx 1 bug bug 0 Jan 29 01:37 /proc/1459/exe -> /home/bug/foo

      Путь к exe вычисляется каждый раз.

      А linkylink это как раз символьная ссылка на каталог, на которых видна разница в поведении cd и других утилит.


      1. TheRipper
        29.01.2016 14:11

        bug@mars:~/bar/five$ ls -l linkylink
        lrwxr-xr-x  1 bug  staff  13 01 янв  2016 linkylink -> ../six/seven/


        1. ValdikSS
          29.01.2016 15:02
          +1

          Нужно это в выводе на картинках сделать, а то действительно непонятно.


          1. TheRipper
            29.01.2016 15:44

            Готово.


      1. TheRipper
        29.01.2016 15:23
        +1

        Хм, я был очень неправ. Оно действительно хранится как путь и каким-то образом обновляется.

        И даже больше, getcwd тоже читает готовый путь.

        Поправлю в статье.


        1. TheRipper
          29.01.2016 15:47

          Возможно что вычисление пути через переход к корню я видел в какой-то реализации libc.


  1. EnterSandman
    29.01.2016 14:12
    +2

    И тут мне вспомнился анекдот про два путя


    1. domix32
      29.01.2016 15:21
      +3

      Травите для незнающих


      1. urticazoku
        29.01.2016 16:17
        +2

        Возможно: «Если вас съели — не отчаивайтесь: у вас всегда есть два выхода.»


    1. stas404
      30.01.2016 20:13

      «Пока я с вами тут шутю, поезд на Воркутю тю-тю»?


  1. kvaps
    29.01.2016 19:46

    если сделать:

    rm -rf /*
    

    — то команда cd будет единственной командой, которая останется работать, казалось бы почему?)


    1. TheRipper
      29.01.2016 20:04
      +2

      Попробовал — нет, pwd, например, тоже работает. Но вот браузер уже действительно почему-то не запускается.


      1. kvaps
        29.01.2016 20:26
        +2

        Ну вообще да, не единственная, вот все которые останутся:

        Скрытый текст
        !          builtin    cp         esac       function   let        pwd        source     ulimit
        ./         caller     declare    eval       getopts    ll         read       suspend    umask
        :          case       dirs       exec       hash       local      readarray  test       unalias
        [          cd         disown     exit       help       logout     readonly   then       unset
        [[         command    do         export     history    ls         return     time       until
        ]]         compgen    done       false      if         mapfile    rm         times      wait
        alias      complete   echo       fc         in         mv         select     trap       while
        bg         compopt    elif       fg         jobs       popd       set        true       {
        bind       continue   else       fi         kill       printf     shift      type       }
        break      coproc     enable     for        l.         pushd      shopt      typeset    
        


        1. TheRipper
          29.01.2016 20:32
          +1

          Они встроены в шелл (причем pwd бывает как встроенная, так и внешняя).
          А вот реализовать cd как внешнюю утилиту невозможно (как культурно можно поменять рабочий каталог у другого процесса?).


        1. amarao
          02.02.2016 18:08

          Ещё останутся самые важные для попытки что-то починить (не после rm -rf /*, конечно) — это '|', '>', '>>', '<'.