¿Qué comando idempotente puedo usar para hacer un enlace simbólico que apunte a un directorio?

16

Quiero poner un comando en un script de shell que creará un enlace simbólico al directorio, pero este script podría ejecutarse una y otra vez, por lo que en las invocaciones posteriores, el comando no debería cambiar nada.

Aquí está la estructura del directorio:

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

Quiero crear un enlace simbólico en foo/fragmentos llamados que apunta al directorio /tmp/test_symlink/repo/resources/snippets.
Entonces corro:

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

lo que da el resultado deseado.

% 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 directorios, 5 archivos

Sin embargo, cuando el comando se ejecuta nuevamente,

% 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'

crea un enlace simbólico a un directorio, donde el enlace simbólico ya existe coloca el enlace simbólico dentro del directorio real

% 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

¿Por qué sucede esto y cómo puedo modificar el comando para que invocaciones posteriores no creen este efecto extraño?

the_velour_fog
fuente

Respuestas:

16

Debe usar la -Topción para esto, le dice lnque siempre trate el nombre del enlace como el nombre del enlace deseado, nunca como un directorio.

Sin esta opción, si le da lnun nombre de enlace que existe como directorio, crea un nuevo enlace al destino dentro de ese directorio.

Tenga en cuenta que -Tes un GNU-ismo (al menos, no está en POSIX), pero ya está utilizando -vque es un GNU-ismo también, así que imagino que no es un problema.

Alternativamente, puede especificar el directorio principal como el nombre del enlace, y el enlace siempre se (re) creará allí:

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

Esto funciona porque su enlace simbólico tiene el mismo nombre que el objetivo.

Stephen Kitt
fuente
gracias, sí, las opciones de GNU no son necesariamente portátiles, pero pueden ser realmente útiles, cuando la portabilidad no es un requisito. Tu respuesta me funciona. De la página del manual -n, --no-dereference treat LINK_NAME as a normal file if it is a symbolic link to a directory. -T, --no-target-directory treat LINK_NAME as a normal file always¿crees que es mejor tratar un enlace simbólico como un archivo siempre? ¿Pensé que sería mejor limitar el uso de estas opciones "especiales"?
the_velour_fog
Si sabe que están disponibles, no hay razón para evitar las opciones específicas de GNU (IMO). Pediste un comando idempotente, así -Tes como lo haces lnidempotente. Hay otras formas de obtener el mismo resultado si lo prefiere, como eliminar el enlace y volver a crearlo, o si ya sabe que fooexiste ln -sfv /tmp/test_symlink/repo/resources/snippets /tmp/test_symlink/foo/(en realidad, esa podría ser una mejor respuesta, lo actualizaré).
Stephen Kitt
1
genial, sí, anteriormente había estado usando ifdeclaraciones para detectar la presencia del enlace simbólico y omitir el comando si el enlace simbólico existía, pero los scripts terminan abarrotados y son difíciles de leer, estaba buscando algo idempotente pero simple, como mkdir -pno usar pruebas , no es lógico, solo un buen revestimiento :)
the_velour_fog
@Stephen Kitt: mencionaste el uso de -T, pero no pude encontrarlo en el fragmento de código de tu respuesta. ¿Estoy malentendido o me falta algo?
Guizmoo03
@ Guizmoo03 tal vez te perdiste la "Alternativa" que presenta el fragmento. Los primeros tres párrafos explican -T, pero el final de mi respuesta presenta otra solución que no utiliza -T.
Stephen Kitt el
-1

Lo mejor que puedo decir es que en la segunda pasada, el lncomando se comporta como cpo mv, y es que si ve un "directorio" en <destination>, colocará el archivo dentro, de lo contrario si <destination> no existe, hará <destination>. (que es lo que evita el truco "slashdot", es decir, se asegurará de que <destination> exista y sea un directorio).
Pero podría estar equivocado.

En cualquier caso, esto parece funcionar

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