Distinguer un fichier normal d'un lien symbolique

22

J'écris un script bash qui doit distinguer un fichier normal d'un lien symbolique. Je pensais que je pouvais le faire avec l'expression if / test, mais cela ne fonctionne pas comme je m'y attendais:

$ 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

Pourquoi donc? Et, comment puis-je le faire correctement?

Nupraptor
la source

Réponses:

20

Il semble que vous brouilliez un peu vos tests. Vous n'avez pas besoin d'exécuter les deux tests, le seul dont vous avez besoin dans ce cas est -hcelui qui vous indique si le fichier est un lien symbolique.

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

Le -ftest vous indique uniquement si l'objet est un fichier. Cela retournerait 0s'il s'agissait d'un répertoire ou d'un nœud de périphérique ou d'un lien symbolique vers un répertoire, mais retournera 1sur un lien symbolique vers un fichier.

Si vous aviez également besoin de savoir s'il s'agissait d'un lien symbolique vers un fichier plutôt qu'un répertoire, vous devrez combiner les résultats des deux tests avec un peu de logique.

Caleb
la source
Comme je l'ai compris dans les documents, la différence entre -eet -fétait celle qui -eétait utilisée pour savoir si un fichier (de tout type) existait, et -fétait spécifiquement pour tester si le fichier existait et était un fichier normal. Il semble que j'ai mal compris ce qu'était un "fichier ordinaire".
Nupraptor
1
@Nupraptor: Oui, vous avez mal compris les documents. Un lien symbolique est considéré comme un fichier normal, contrairement à certains des autres types de nœuds qu'un fichier pourrait être (nœud de périphérique de bloc, nœud de périphérique de caractère, répertoire, etc.). Si vous voulez savoir de quel type de fichier il s'agit, vous devez exécuter un test spécifique au type de fichier comme -hpour les liens symboliques, -ppour les canaux nommés, etc.
Caleb
Ensuite, comment puis-je tester si un fichier est un fichier normal dans le sens où ce n'est pas un canal ni un lien symbolique, etc.? Dois-je ouvrir une autre question à ce sujet?
Nupraptor
@Nupraptor: Le seul cas étrange est un lien symbolique qui renvoie à un fichier normal. Sinon, s'il teste en tant que fichier normal, c'est un fichier normal.
David Schwartz
3
Le répertoire test -f renvoie un 1 et non un 0: test -f. ; écho $? (sorties 1)
polynôme
7

@Caleb a raison de faire en sorte que le script teste simplement le lien symbolique. Cependant, la partie sur pourquoi a été laissée de côté et j'étais curieux. Si vous regardez le code source de coreutils et étirez la sortie de test, vous pouvez voir que lorsque vous exécutez le test de lien symbolique, il utilise lstat et si vous utilisez le test -f, il appelle en fait 'stat' qui suit le lien symbolique:

$ 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

Depuis la page de manuel de 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.

Cela signifie que le test -f retournera vrai tant que le nom de fichier spécifié est un lien symbolique vers un fichier normal ou un fichier normal lui-même.

polynôme
la source