Какую команду idempotent можно использовать, чтобы символическая ссылка указывала на каталог?

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

Вот структура каталогов:

% tree /tmp/test_symlink /tmp/test_symlink ├── foo └── repo └── resources └── snippets ├── php.snippets ├── sh.snippets ├── snippets.snippets ├── sql.snippets └── vim.snippets 

Я хочу создать символическую ссылку в foo/ called snippets, которая указывает на каталог /tmp/test_symlink/repo/resources/snippets .
Поэтому я запускаю:

 % ln -sfv /tmp/test_symlink/repo/resources/snippets /tmp/test_symlink/foo/snippets '/tmp/test_symlink/foo/snippets' -> '/tmp/test_symlink/repo/resources/snippets' 

который дает желаемый результат.

 % tree /tmp/test_symlink /tmp/test_symlink ├── foo │  └── snippets -> /tmp/test_symlink/repo/resources/snippets └── repo └── resources └── snippets ├── php.snippets ├── sh.snippets ├── snippets.snippets ├── sql.snippets └── vim.snippets 

5 каталогов, 5 файлов

Однако, когда команда запускается снова,

 % ln -sfv /tmp/test_symlink/repo/resources/snippets /tmp/test_symlink/foo/snippets '/tmp/test_symlink/foo/snippets/snippets' -> '/tmp/test_symlink/repo/resources/snippets' 

он создает символическую ссылку на каталог, где существует символическая ссылка aleady помещает символическую ссылку в реальный каталог

 % tree /tmp/test_symlink /tmp/test_symlink ├── foo │  └── snippets -> /tmp/test_symlink/repo/resources/snippets └── repo └── resources └── snippets ├── php.snippets ├── sh.snippets ├── snippets -> /tmp/test_symlink/repo/resources/snippets ├── snippets.snippets ├── sql.snippets └── vim.snippets 

почему это происходит и как я могу изменить команду, чтобы последующие вызовы не создавали этот странный эффект?

Вы должны использовать параметр -T для этого, он сообщает ln чтобы всегда обрабатывать имя ссылки как имя требуемой ссылки, а не как каталог.

Без этой опции, если вы укажете ln имя ссылки, которое существует как каталог, оно создает новую ссылку на цель внутри этого каталога.

Обратите внимание: -T является GNU-ism (по крайней мере, он не в POSIX), но вы уже используете -v который является GNU-ism, поэтому я думаю, что это не проблема.

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

 ln -sfv /tmp/test_symlink/repo/resources/snippets /tmp/test_symlink/foo/ 

Это работает, потому что ваша символическая ссылка имеет то же имя, что и цель.

Лучше всего я могу сказать, что происходит здесь, так это то, что на втором проходе команда ln ведет себя как cp или mv , а это значит, что если он увидит «каталог» в <destination>, он поместит файл внутри него, иначе, если <destination> не существует, он сделает <destination>. (это то, что избегает трюк «slashdot» – то есть он будет гарантировать, что <destination> существует и является каталогом).
Но я могу ошибаться.

В любом случае это, похоже, работает

 ln -sfv --no-dereference /tmp/test_symlink/repo/resources/snippets/ foo/snippets