Distinguindo um arquivo regular de um link simbólico

22

Estou escrevendo um script bash que precisa distinguir um arquivo regular de um link simbólico. Eu pensei que poderia fazer isso com a expressão if / test, mas não funciona como eu esperava:

$ touch regular_file
$ test -f regular_file; echo $?
0
$ test -h regular_file; echo $?
1
$ ln -s regular_file symlink
$ test -h symlink; echo $?
0
$ test -f symlink; echo $?
0

Por que é que? E como posso fazer isso corretamente?

Nupraptor
fonte

Respostas:

20

Parece que você está apenas mexendo um pouco nos testes. Você não precisa executar os dois testes, o único necessário para este caso é o -hque lhe diz se o arquivo é um link simbólico.

test -h file && echo "is symlink" || echo "is regular file"

O -fteste apenas informa se o objeto é um arquivo. Isso retornaria 0se fosse um diretório ou um nó de dispositivo ou um link simbólico para um diretório, mas retornará 1em um link simbólico para um arquivo.

Se você também precisasse saber se era um link simbólico para um arquivo em vez de um diretório, seria necessário combinar os resultados dos dois testes com um pouco de lógica.

Caleb
fonte
Como eu entendi nos documentos, a diferença entre -ee -ffoi -eusada para saber se existia um arquivo (de qualquer tipo) e -fera especificamente para testar se o arquivo existia e era um arquivo normal. Parece que eu mal o que um "arquivo regular" era ..
Nupraptor
1
@ Nupraptor: Sim, você não entendeu os documentos. Um link simbólico é considerado um arquivo regular, em oposição a alguns dos outros tipos de nós que um arquivo pode ser (nó de dispositivo de bloco, nó de dispositivo de caractere, diretório, etc.). Se você quer saber que tipo de arquivo que é que você tem que executar um teste de tipo de arquivo específico, como -hpara links simbólicos, -ppara pipes nomeados, etc.
Caleb
Então, como faço para testar se um arquivo é regular no sentido de que não é um pipe nem um link simbólico, etc.? Devo abrir outra pergunta para isso?
Nupraptor
@ Nupraptor: O único caso estranho é um link simbólico vinculado a um arquivo comum. Caso contrário, se testar como um arquivo regular, é um arquivo regular.
David Schwartz
3
O diretório test -f retornará 1 e não 0: test -f. ; eco $? (outputs 1)
polinômio
7

O @Caleb está correto ao fazer o script apenas testar o link simbólico. No entanto, a parte sobre o porquê foi deixada de fora e eu fiquei curioso. Se você olhar o código fonte do coreutils e rastrear a saída do teste, poderá ver que, quando executa o teste do link simbólico, ele usa lstat e, se você usa o teste -f, na verdade, chama 'stat', que segue o link simbólico:

$ ln -s varnish_config XXX
$ strace -s 2000 test -L XXX 2>&1 | grep XXX
execve("/usr/bin/test", ["test", "-L", "XXX"], [/* 47 vars */]) = 0
lstat("XXX", {st_mode=S_IFLNK|0777, st_size=14, ...}) = 0

$ strace -s 2000 test -L varnish_config 2>&1 | grep varnish
execve("/usr/bin/test", ["test", "-L", "varnish_config"], [/* 47 vars */]) = 0
lstat("varnish_config", {st_mode=S_IFREG|0664, st_size=1046, ...}) = 0

$ strace -s 2000 test -f XXX 2>&1 | grep XXX
execve("/usr/bin/test", ["test", "-f", "XXX"], [/* 47 vars */]) = 0
stat("XXX", {st_mode=S_IFREG|0664, st_size=1046, ...}) = 0

Na página do manual stat:

   stat() stats the file pointed to by path and fills in buf.

   lstat() is identical to stat(), except that if path is a symbolic link,
   then the link itself is stat-ed, not the file that it refers to.

Isso significa que o teste -f retornará verdadeiro, desde que o nome do arquivo especificado seja um link simbólico para um arquivo comum ou para um arquivo comum.

polinomial
fonte