GNU/Linux >> Tutoriales Linux >  >> Linux

Recursión de enlace simbólico:¿qué hace que se "reinicie"?

Escribí un pequeño script bash para ver qué sucede cuando sigo siguiendo un enlace simbólico que apunta al mismo directorio. Esperaba que creara un directorio de trabajo muy largo o que fallara. Pero el resultado me sorprendió…

mkdir a
cd a

ln -s ./. a

for i in `seq 1 1000`
do
  cd a
  pwd
done

Parte de la salida es

${HOME}/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
${HOME}/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
${HOME}/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
${HOME}/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
${HOME}/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
${HOME}/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a/a
${HOME}/a
${HOME}/a/a
${HOME}/a/a/a
${HOME}/a/a/a/a
${HOME}/a/a/a/a/a
${HOME}/a/a/a/a/a/a
${HOME}/a/a/a/a/a/a/a
${HOME}/a/a/a/a/a/a/a/a

¿Qué está pasando aquí?

Respuesta aceptada:

Patrice identificó la fuente del problema en su respuesta, pero si quiere saber cómo llegar desde allí hasta por qué lo obtiene, esta es la historia larga.

El directorio de trabajo actual de un proceso no es nada que le parezca demasiado complicado. Es un atributo del proceso que es un identificador de un archivo de tipo directorio desde donde comienzan las rutas relativas (en las llamadas al sistema realizadas por el proceso). Al resolver una ruta relativa, el kernel no necesita conocer la (a) ruta completa a ese directorio actual, simplemente lee las entradas del directorio en ese archivo de directorio para encontrar el primer componente de la ruta relativa (y .. es como cualquier otro archivo en ese sentido) y continúa desde allí.

Ahora, como usuario, a veces le gusta saber dónde se encuentra ese directorio en el árbol de directorios. Con la mayoría de Unices, el árbol de directorios es un árbol, sin bucles. Es decir, solo hay una ruta desde la raíz del árbol (/ ) a cualquier archivo dado. Ese camino generalmente se llama el camino canónico.

Para obtener la ruta del directorio de trabajo actual, lo que tiene que hacer un proceso es subir (bueno, bajar si te gusta ver un árbol con su raíz en la parte inferior) el árbol vuelve a la raíz, encontrando los nombres de los nodos en el camino.

Por ejemplo, un proceso que intenta averiguar que su directorio actual es /a/b/c , abriría el .. directorio (ruta relativa, entonces .. es la entrada en el directorio actual) y busque un archivo de tipo directorio con el mismo número de inodo que . , descubre que c coincide, luego abre ../.. y así sucesivamente hasta que encuentre / . No hay ambigüedad allí.

Eso es lo que getwd() o getcwd() Las funciones de C hacen o al menos solían hacer.

En algunos sistemas como Linux moderno, hay una llamada al sistema para devolver la ruta canónica al directorio actual que realiza esa búsqueda en el espacio del kernel (y le permite encontrar su directorio actual incluso si no tiene acceso de lectura a todos sus componentes) , y eso es lo que getcwd() llamadas allí. En Linux moderno, también puede encontrar la ruta al directorio actual a través de readlink() en /proc/self/cwd .

Eso es lo que hacen la mayoría de los lenguajes y shells iniciales al devolver la ruta al directorio actual.

En tu caso, puedes llamar a cd a tantas veces como quieras, porque es un enlace simbólico a . , el directorio actual no cambia, por lo que todo getcwd() , pwd -P , python -c 'import os; print os.getcwd()' , perl -MPOSIX -le 'print getcwd' devolvería su ${HOME} .

Ahora, los enlaces simbólicos complicaron todo eso.

symlinks permitir saltos en el árbol de directorios. En /a/b/c , si /a o /a/b o /a/b/c es un enlace simbólico, entonces la ruta canónica de /a/b/c sería algo completamente diferente. En particular, el .. entrada en /a/b/c no es necesariamente /a/b .

En el shell de Bourne, si lo hace:

cd /a/b/c
cd ..

O incluso:

cd /a/b/c/..

No hay garantía de que termines en /a/b .

Al igual que:

vi /a/b/c/../d

no es necesariamente lo mismo que:

vi /a/b/d

ksh introdujo un concepto de un directorio de trabajo actual lógico para solucionarlo de alguna manera. La gente se acostumbró y POSIX terminó especificando ese comportamiento, lo que significa que la mayoría de los shells hoy en día también lo hacen:

Relacionado:Linux:¿entiende cómo iniciar sesión en Linux?

Para el cd y pwd comandos incorporados (y solo para ellos (aunque también para popd /pushd en shells que los tienen)), el shell mantiene su propia idea del directorio de trabajo actual. Está almacenado en el $PWD variable especial.

Cuando lo haces:

cd c/d

incluso si c o c/d son enlaces simbólicos, mientras que $PWD contiene /a/b , agrega c/d hasta el final, así que $PWD se convierte en /a/b/c/d . Y cuando lo hagas:

cd ../e

En lugar de hacer chdir("../e") , hace chdir("/a/b/c/e") .

Y el pwd El comando solo devuelve el contenido del $PWD variables.

Eso es útil en shells interactivos porque pwd genera una ruta al directorio actual que brinda información sobre cómo llegó allí y siempre que solo use .. en argumentos a cd y no otros comandos, es menos probable que te sorprenda, porque cd a; cd .. o cd a/.. generalmente te llevaría de vuelta a donde estabas.

Ahora, $PWD no se modifica a menos que hagas un cd . Hasta la próxima vez que llames a cd o pwd , pueden pasar muchas cosas, cualquiera de los componentes de $PWD podría renombrarse. El directorio actual nunca cambia (siempre es el mismo inodo, aunque podría eliminarse), pero su ruta en el árbol de directorios podría cambiar por completo. getcwd() calcula el directorio actual cada vez que se llama recorriendo el árbol de directorios para que su información sea siempre precisa, pero para el directorio lógico implementado por shells POSIX, la información en $PWD podría volverse obsoleto. Así que al ejecutar cd o pwd , algunos proyectiles pueden querer protegerse contra eso.

En ese caso particular, ves diferentes comportamientos con diferentes shells.

A algunos les gusta ksh93 ignore el problema por completo, por lo que devolverá información incorrecta incluso después de llamar a cd (y no vería el comportamiento que está viendo con bash allí).

A algunos les gusta bash o zsh comprueba que $PWD sigue siendo una ruta al directorio actual en cd , pero no sobre pwd .

pdksh comprueba ambos pwd y cd (pero sobre pwd , no actualiza $PWD )

ash (al menos el que se encuentra en Debian) no verifica, y cuando lo hace cd a , en realidad hace cd "$PWD/a" , por lo que si el directorio actual ha cambiado y $PWD ya no apunta al directorio actual, en realidad no cambiará al a directorio en el directorio actual, pero el que está en $PWD (y devolver un error si no existe).

Si quieres jugar con él, puedes hacer:

cd
mkdir -p a/b
cd a
pwd
mv ~/a ~/b 
pwd
echo "$PWD"
cd b
pwd; echo "$PWD"; pwd -P # (and notice the bug in ksh93)

en varias conchas.

En tu caso, ya que estás usando bash , después de un cd a , bash comprueba que $PWD todavía apunta al directorio actual. Para hacer eso, llama a stat() sobre el valor de $PWD para verificar su número de inodo y compararlo con el de . .

Pero cuando la búsqueda de $PWD ruta implica resolver demasiados enlaces simbólicos, que stat() regresa con un error, por lo que el shell no puede verificar si $PWD todavía corresponde al directorio actual, por lo que lo calcula de nuevo con getcwd() y actualiza $PWD en consecuencia.

Ahora, para aclarar la respuesta de Patrice, esa verificación de la cantidad de enlaces simbólicos encontrados al buscar una ruta es para protegerse contra los bucles de enlaces simbólicos. El bucle más simple se puede hacer con

rm -f a b
ln -s a b
ln -s b a

Sin esa protección, en un cd a/x , el sistema tendría que encontrar donde a enlaza a, encuentra que es b y es un enlace simbólico que enlaza con a , y eso continuaría indefinidamente. La forma más sencilla de protegerse contra eso es darse por vencido después de resolver más de un número arbitrario de enlaces simbólicos.

Relacionado:Steam:¿se aplican modificaciones en varias computadoras cuando vinculo la cuenta de Nexus a la cuenta de Steam?

Ahora regrese al directorio de trabajo actual lógico y por qué no es una característica tan buena. Es importante darse cuenta de que es solo para cd en el shell y no en otros comandos.

Por ejemplo:

cd -- "$dir" &&  vi -- "$file"

no siempre es lo mismo que:

vi -- "$dir/$file"

Es por eso que a veces encontrarás que la gente recomienda usar siempre cd -P en scripts para evitar confusiones (no desea que su software maneje un argumento de ../x de manera diferente a otros comandos solo porque está escrito en shell en lugar de en otro idioma).

El -P opción es deshabilitar el directorio lógico manejando así cd -P -- "$var" en realidad llama a chdir() sobre el contenido de $var (al menos mientras $CDPATH no se establece, y excepto cuando $var es - (o posiblemente -2 , +3 … en algunas conchas) pero esa es otra historia). Y después de un cd -P , $PWD contendrá una ruta canónica.


Linux
  1. Creando un nuevo directorio en C

  2. Node.js:compruebe si el archivo es un enlace simbólico al iterar sobre el directorio con 'fs'

  3. Agregue un script bash a la ruta

  4. ¿Qué genera pwd?

  5. Creando un programa en bin

¿Qué hace que Linux sea el sistema operativo sostenible?

¿Cd a un directorio de nombre desconocido en una ruta conocida?

¿Qué son los enlaces simbólicos en Linux? ¿Cómo crear enlaces simbólicos?

Ruta absoluta vs relativa en Linux:¿Cuál es la diferencia?

Linux:agregar un directorio a PATH

Uso de / al usar cd