web-dev-qa-db-fra.com

Quelle est la différence entre "cat <filename" et "cat filename"?

La façon la plus simple d'afficher le contenu d'un fichier est d'utiliser la commande cat:

cat file.txt

Je peux obtenir le même résultat en utilisant la redirection d'entrée:

cat < file.txt

Alors, quelle est la différence entre eux?

26
BlueSkies

Il n'y a aucune différence d'un point de vue utilisateur. Ces commandes font la même chose.

Techniquement, la différence réside dans le programme qui ouvre le fichier: le programme cat ou le shell qui l'exécute. Les redirections sont définies par le shell, avant d'exécuter une commande.

(Ainsi, dans certaines commandes other - c'est-à-dire not la commande indiquée dans la question - il peut y avoir une différence. En particulier, si vous ne pouvez pas accéder à file.txt mais l'utilisateur root peut, puis Sudo cat file.txt fonctionne mais Sudo cat < file.txt ne fait pas.)

Vous pouvez utiliser l'un ou l'autre qui est pratique dans votre cas.

Il existe presque toujours plusieurs façons d'obtenir le même résultat.

cat accepte un fichier d'arguments ou stdin s'il n'y a pas d'arguments.

Voir man cat:

SYNOPSIS
       cat [OPTION]... [FILE]...

DESCRIPTION
       Concatenate FILE(s) to standard output.

       With no FILE, or when FILE is -, read standard input.
14
Pilot6
cat file

Le programme cat ouvrira, lira et fermera le fichier.

cat < file

Votre Shell ouvrira le fichier et connectera le contenu au stdin de cat. cat reconnaît qu'il n'a aucun argument de fichier et lira à partir de stdin.

59
glenn jackman

Une grande différence

Une grande différence est avec le *, ?, ou [ globbing caractères (caractères génériques) ou tout autre élément que le shell peut développer en plusieurs noms de fichiers. Tout ce que le Shell développe en deux éléments ou plus, plutôt que de le traiter comme un seul nom de fichier, ne peut pas être ouvert pour la redirection.

Sans redirection (c'est-à-dire sans <), le shell passe plusieurs noms de fichiers à cat, qui sort le contenu des fichiers les uns après les autres. Par exemple, cela fonctionne:

$ ls hello?.py
hello1.py  hello2.py

$ cat hello?.py

# Output for two files 'hello1.py' and 'hello2.py' appear on your screen

Mais avec redirection (<) un message d'erreur se produit:

$ ls < hello?.py
bash: hello?.py: ambiguous redirect

$ cat < hello?.py
bash: hello?.py: ambiguous redirect

Une toute petite différence

Je pensais qu'avec la redirection ce serait plus lent mais il n'y a pas de différence de temps perceptible:

$ time for f in * ; do cat "$f" > /dev/null ; done

real    0m3.399s
user    0m0.130s
sys     0m1.940s

$ time for f in * ; do cat < "$f" > /dev/null ; done

real    0m3.430s
user    0m0.100s
sys     0m2.043s

Notes:

  • La différence est d'environ 1/1000e (1 millième) de seconde dans ce test. Dans d'autres tests, il était de 1/100e de seconde, ce qui n'est toujours pas visible.
  • Alterner les tests plusieurs fois pour que les données soient mises en cache dans RAM autant que possible et des temps de comparaison plus cohérents sont retournés. Une autre option consiste à supprimer tous les caches avant chaque test.
14
WinEunuuchs2Unix

La principale différence est de savoir qui ouvre le fichier, Shell ou cat. Ils peuvent fonctionner avec différents régimes d'autorisation, donc

Sudo cat /proc/some-protected-file

peut fonctionner pendant

Sudo cat < /proc/some-protected-file

échouera. Ce type de régime d'autorisation peut être un peu difficile à contourner lorsque vous souhaitez simplement utiliser echo pour faciliter l'écriture de scripts, il y a donc la possibilité de mal utiliser tee comme dans

echo level 7|Sudo tee /proc/acpi/ibm/fan

ce qui ne fonctionne pas vraiment en utilisant la redirection à la place à cause du problème d'autorisation.

10
user1018475

TL; DR Version de la réponse:

  • Avec cat file.txt, L'application (dans ce cas cat) a reçu un paramètre positionnel, exécute syscall open (2) dessus et des vérifications de permission ont lieu dans les applications.

  • Avec cat < file.txt, Le shell exécutera dup2() syscall pour faire de stdin une copie du descripteur de fichier (généralement le prochain disponible, par exemple 3) correspondant à file.txt Et fermera ce descripteur de fichier (par exemple 3). L'application n'effectue pas open (2) sur le fichier et ignore l'existence du fichier; il fonctionne strictement sur son descripteur de fichier stdin. La vérification des autorisations incombe au shell. La description du fichier ouvert restera la même que lorsque le shell a ouvert le fichier.

Introduction

En surface, cat file.txt Et cat < file.txt Se comportent de la même façon, mais il y a beaucoup plus de choses en coulisse avec cette différence de caractère unique. Ce caractère < Change la façon dont Shell comprend file.txt, Qui ouvre le fichier et comment le fichier est transmis entre Shell et la commande. Bien sûr, afin d'expliquer tous ces détails, nous devons également comprendre comment fonctionne l'ouverture des fichiers et l'exécution des commandes dans Shell, et c'est ce que ma réponse vise à atteindre - éduquer le lecteur, en termes les plus simples possibles, sur ce qui se passe réellement dans ces commandes apparemment simples. Dans cette réponse, vous trouverez plusieurs exemples, y compris ceux qui utilisent la commande strace pour sauvegarder les explications de ce qui se passe réellement dans les coulisses.

Parce que le fonctionnement interne des shells et des commandes est basé sur des appels système standard, il est important de visualiser cat comme une seule commande parmi tant d'autres. Si vous êtes un débutant lisant cette réponse, veuillez vous fixer avec un esprit ouvert et être conscient que prog file.txt Ne sera pas toujours le même que prog < file.txt. Une commande différente peut se comporter entièrement différemment lorsque les deux formulaires lui sont appliqués, et cela dépend des autorisations ou de la façon dont le programme est écrit. Je vous demande également de suspendre votre jugement et de considérer cela du point de vue de différents utilisateurs - pour un utilisateur Shell occasionnel, les besoins peuvent être entièrement différents de ceux d'un administrateur système et d'un développeur.

execve () Syscall et paramètres positionnels que l'exécutable voit

Les shells exécutent des commandes en créant un processus enfant avec fork (2) syscall et en appelant execve (2) syscall, qui exécute la commande avec les arguments et les variables d'environnement spécifiés. La commande appelée inside execve() prendra le relais et remplacera le processus; par exemple, lorsque Shell appelle cat, il crée d'abord un processus enfant avec le PID 12345 et après que execve() se produit, le PID 12345 devient cat.

Cela nous amène à la différence entre cat file.txt Et cat < file.txt. Dans le premier cas, cat file.txt Est une commande appelée avec un paramètre positionnel, et le Shell assemblera execve() de manière appropriée:

$ strace -e execve cat testfile.txt
execve("/bin/cat", ["cat", "testfile.txt"], 0x7ffcc6ee95f8 /* 50 vars */) = 0
hello, I am testfile.txt
+++ exited with 0 +++

Dans le second cas, la partie < Est l'opérateur Shell et < testfile.txt Dit au Shell d'ouvrir testfile.txt Et de faire du descripteur de fichier stdin 0 une copie du descripteur de fichier qui correspond à testfile.txt. Cela signifie que < testfile.txt Ne sera pas transmis à la commande elle-même comme argument positionnel:

$ strace -e execve cat < testfile.txt
execve("/bin/cat", ["cat"], 0x7ffc6adb5490 /* 50 vars */) = 0
hello, I am testfile.txt
+++ exited with 0 +++
$ 

Cela peut être important si le programme nécessite un paramètre positionnel pour fonctionner correctement. Dans ce cas, cat accepte par défaut l'entrée de stdin si aucun paramètre de position correspondant aux fichiers n'a été fourni. Ce qui nous amène également au sujet suivant: stdin et descripteurs de fichiers.

STDIN et descripteurs de fichiers

Qui ouvre le fichier - cat ou Shell? Comment l'ouvrent-ils? Ont-ils même la permission de l'ouvrir? Ce sont les questions qui peuvent être posées, mais nous devons d'abord comprendre comment fonctionne l'ouverture d'un fichier.

Lorsqu'un processus exécute open() ou openat() sur un fichier, ces fonctions fournissent au processus un entier correspondant au fichier ouvert, et les programmes peuvent alors appeler read() , seek() et write() appels et une myriade d'autres appels système en faisant référence à ce nombre entier. Bien sûr, le système (alias noyau) gardera en mémoire comment un fichier particulier a été ouvert, avec quel type d'autorisations, avec quel type de mode - lecture seule, écriture seule, lecture/écriture - et où dans le fichier nous sommes actuellement - à l'octet 0 ou à l'octet 1024 - qui est appelé décalage. Cela s'appelle description du fichier ouvert.

Au niveau très basique, cat testfile.txt Est l'endroit où cat ouvre le fichier et il sera référencé par le prochain descripteur de fichier disponible qui est 3 (notez le 3 dans lire (2) ).

$ strace -e read -f cat testfile.txt > /dev/null
...
read(3, "hello, I am testfile.txt and thi"..., 131072) = 79
read(3, "", 131072)                     = 0
+++ exited with 0 +++

En revanche, cat < testfile.txt Utilisera le descripteur de fichier 0 (alias stdin):

$ strace -e read -f cat < testfile.txt > /dev/null
...
read(0, "hello, I am testfile.txt and thi"..., 131072) = 79
read(0, "", 131072)                     = 0
+++ exited with 0 +++

Vous vous souvenez quand plus tôt nous avons appris que les shells exécutent des commandes via fork() d'abord puis exec() type de processus? Eh bien, il s'avère que comment le fichier est ouvert carie sur les processus enfants créés avec le modèle fork()/exec(). Pour citer ouvrir (2) manuel :

Lorsqu'un descripteur de fichier est dupliqué (en utilisant dup (2) ou similaire), le doublon fait référence à la même description de fichier ouvert que le descripteur de fichier d'origine, et les deux descripteurs de fichiers partagent par conséquent les indicateurs de décalage et d'état de fichier. Un tel partage peut également se produire entre les processus: un processus enfant créé via fork (2) hérite les doublons des descripteurs de fichiers de son parent, et ces doublons font référence aux mêmes descriptions de fichiers ouverts

Qu'est-ce que cela signifie pour cat file.txt Vs cat < file.txt? Beaucoup en fait. Dans cat file.txt, Le cat ouvre le fichier, ce qui signifie qu'il contrôle la façon dont le fichier est ouvert. Dans le second cas, Shell ouvrira le file.txt Et comment il a été ouvert restera inchangé pour les processus enfants, les commandes composées et les pipelines. Où nous en sommes actuellement dans le fichier restera également le même.

Utilisons ce fichier comme exemple:

$ cat testfile.txt 
hello, I am testfile.txt and this is first line
line two
line three

last line

Regardez l'exemple ci-dessous. Pourquoi le mot line n'a-t-il pas changé dans la première ligne?

$ { head -n1; sed 's/line/potato/'; }  <  testfile.txt 2>/dev/null
hello, I am testfile.txt and this is first line
potato two
potato three

last potato

La réponse réside dans la citation de open (2) manuel ci-dessus: le fichier ouvert par le shell est dupliqué sur stdin de la commande composée et chaque commande/processus qui s'exécute partage l'offset de la description du fichier ouvert . head a simplement rembobiné le fichier en avant d'une ligne, et sed s'est occupé du reste. Plus précisément, nous verrions 2 séquences de syscalls dup2()/fork()/execve(), et dans chaque cas, nous obtenons la copie du descripteur de fichier qui fait référence au même description du fichier sur le dossier ouvert testfile.txt. Confus ? Prenons un exemple un peu plus fou:

$ { head -n1; dd of=/dev/null bs=1 count=5; cat; }  <  testfile.txt 2>/dev/null
hello, I am testfile.txt and this is first line
two
line three

last line

Ici, nous avons imprimé la première ligne, puis rembobiné la description du fichier ouvert 5 octets à l'avance (ce qui a éliminé le mot line), puis nous avons simplement imprimé le reste. Et comment avons-nous réussi à le faire? La description du fichier ouvert sur testfile.txt Reste la même, avec un décalage partagé sur le fichier.

Maintenant, pourquoi cela est utile à comprendre, à part écrire des commandes composées folles comme ci-dessus? En tant que développeur, vous souhaiterez peut-être profiter de ce comportement ou vous en méfier. Disons qu'au lieu de cat vous avez écrit un programme C qui a besoin d'une configuration passée en fichier ou passée de stdin, et vous l'exécutez comme myprog myconfig.json. Que se passera-t-il si vous exécutez à la place { head -n1; myprog;} < myconfig.json? Au mieux, votre programme obtiendra des données de configuration incomplètes, et au pire - interrompez le programme. Nous pouvons également utiliser cela comme un avantage pour générer un processus enfant et permettre aux parents de revenir en arrière aux données dont le processus enfant devrait s'occuper.

Autorisations et privilèges

Commençons par un exemple cette fois sur un fichier sans autorisation de lecture ou d'écriture pour les autres utilisateurs:

$ Sudo -u potato cat < testfile.txt
hello, I am testfile.txt and this is first line
line two
line three

last line
$ Sudo -u potato cat testfile.txt
cat: testfile.txt: Permission denied

Que s'est-il passé ici ? Pourquoi pouvons-nous lire le fichier dans le premier exemple en tant qu'utilisateur potato mais pas dans le second? Cela revient à la même citation de la page de manuel open (2) mentionnée précédemment. Avec < file.txt Shell ouvre le fichier, d'où les vérifications de permission se produisent au moment de open/openat() effectuée par Shell. Le shell à ce moment-là s'exécute avec les privilèges du propriétaire du fichier qui dispose des autorisations de lecture sur le fichier. En raison de l'héritage de la description du fichier ouvert entre les appels dup2, Le shell transmet la copie du descripteur de fichier ouvert à Sudo, qui transmet la copie du descripteur de fichier à cat et cat ne sachant rien d'autre lit joyeusement le contenu du fichier. Dans la dernière commande, l'utilisateur cat sous potato exécute open() sur le fichier, et bien sûr, cet utilisateur n'a pas l'autorisation de lire le fichier.

Plus concrètement et plus communément, c'est pourquoi les utilisateurs sont perplexes quant à la raison pour laquelle quelque chose comme ça ne fonctionne pas (exécution d'une commande privilégiée pour écrire dans un fichier qu'ils ne peuvent pas ouvrir):

$ Sudo echo 100 > /sys/class/drm/*/intel_backlight/brightness
bash: /sys/class/drm/card0-eDP-1/intel_backlight/brightness: Permission denied

Mais quelque chose comme ça fonctionne (en utilisant une commande privilégiée pour écrire dans un fichier qui nécessite des privilèges):

$ echo 100 |Sudo tee /sys/class/drm/*/intel_backlight/brightness
[Sudo] password for administrator: 
100

Un exemple théorique de la situation opposée à celle que j'ai montrée précédemment (où privileged_prog < file.txt Échoue mais privileged_prog file.txt Fonctionne) serait avec les programmes SUID. Les programmes SUID , tels que passwd, permettent d'effectuer des actions avec les autorisations du propriétaire de l'exécutable. C'est pourquoi la commande passwd vous permet de changer votre mot de passe puis d'écrire cette modification dans / etc/shadow même si le fichier appartient à l'utilisateur root.

Et par souci d'exemple et de plaisir, j'écris en fait une démo rapide cat - comme une application en C ( code source ici) avec le bit SUID défini, mais si vous obtenez le point - ressentez libre de passer à la section suivante de cette réponse et d'ignorer cette partie. Note latérale: le système d'exploitation ignore le bit SUID sur les exécutables interprétés avec #!, Donc une version Python de cette même chose échouerait).

Vérifions les autorisations sur le programme et le testfile.txt:

$ ls -l ./privileged
-rwsr-xr-x 1 administrator administrator 8672 Nov 29 16:39 ./privileged
$ ls -l testfile.txt
-rw-r----- 1 administrator administrator 79 Nov 29 12:34 testfile.txt

Semble bon, seuls le propriétaire du fichier et ceux qui appartiennent au groupe administrator peuvent lire ce fichier. Maintenant, connectons-nous en tant qu'utilisateur de pomme de terre et essayons de lire le fichier:

$ su potato
Password: 
potato@my-PC:/home/administrator$ cat ./testfile.txt
cat: ./testfile.txt: Permission denied
potato@my-PC:/home/administrator$ cat  < ./testfile.txt
bash: ./testfile.txt: Permission denied

Ça a l'air bien, ni Shell ni cat qui ont des droits d'utilisateur de pomme de terre ne peuvent lire le fichier qu'ils ne sont pas autorisés à lire. Notez également qui a signalé l'erreur - cat vs bash. Testons notre programme SUID:

potato@my-PC:/home/administrator$ ./privileged testfile.txt
hello, I am testfile.txt and this is first line
line two
line three

last line
potato@my-PC:/home/administrator$ ./privileged < testfile.txt
bash: testfile.txt: Permission denied

Fonctionne comme prévu! Encore une fois, le point soulevé par cette petite démo est que prog file.txt Et prog < file.txt Diffèrent dans qui ouvre le fichier et diffèrent dans les autorisations de fichier ouvert.

Comment les programmes React to STDIN

Nous savons déjà que < testfile.txt Réécrit stdin de telle manière que les données proviendront du fichier spécifié au lieu du clavier. En théorie, et basés sur la philosophie Unix de "faire une chose et bien le faire", les programmes lisant depuis stdin (aka descripteur de fichier 0) devraient se comporter de manière cohérente, et en tant que tel, prog1 | prog2 Devrait être similaire à prog2 file.txt. Mais que se passe-t-il si prog2 Veut rembobiner avec lseek syscall, par exemple pour passer à certains octets ou rembobiner jusqu'à la fin afin de trouver la quantité de données dont nous disposons) ?

Certains programmes n'autorisent pas la lecture des données du tuyau, car les pipelines ne peuvent pas être rembobinés avec lseek (2) syscall ou les données ne peuvent pas être chargées en mémoire avec mmap (2) pour un traitement plus rapide. Cela a été couvert par une excellente réponse de Stephane Chazelas dans cette question: Quelle est la différence entre "cat file | ./binary" et "./binary <file"? = Je recommande fortement de lire cela.

Heureusement, cat < file.txt Et cat file.txt Se comportent de manière cohérente et cat n'est en aucun cas contre les canaux, bien que nous sachions qu'il lit des descripteurs de fichiers entièrement différents. Comment cela s'applique-t-il dans prog file.txt Contre prog < file.txt En général? Si un programme ne veut vraiment rien faire avec les tuyaux, le paramètre de position manquant file.txt Suffira pour quitter avec erreur, mais l'application peut toujours utiliser lseek() sur stdin pour le vérifier est un tuyau ou non (bien que isatty (3) ou la détection du mode S_ISFIFO dans fstat (2) soit plus susceptible d'être utilisé pour détecter l'entrée de tuyau), auquel cas faire quelque chose comme ./binary <(grep pattern file.txt) ou ./binary < <(grep pattern file.txt) peut ne pas fonctionner.

Influence du type de fichier

Un type de fichier peut influencer le comportement de prog file Par rapport à prog < file. Ce qui implique dans une certaine mesure qu'en tant qu'utilisateur d'un programme, vous choisissez les appels système même si vous n'en avez pas conscience. Par exemple, supposons que nous ayons une socket de domaine Unix et que nous exécutions le serveur nc pour l'écouter, peut-être avons-nous même préparé des données à servir

$ nc -U -l /tmp/mysocket.sock   < testfile.txt 

Dans ce cas, /tmp/mysocket.sock Sera ouvert via différents appels système:

socket(AF_UNIX, SOCK_STREAM, 0)         = 3
setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
bind(3, {sa_family=AF_UNIX, Sun_path="/tmp/mysocket.sock"}, 20) = 0

Maintenant, essayons de lire les données de ce socket dans un terminal différent:

$ cat /tmp/mysocket.sock
cat: /tmp/mysocket.sock: No such device or address
$ cat <  /tmp/mysocket.sock
bash: /tmp/mysocket.sock: No such device or address

Le shell et le chat exécutent tous les deux open(2) syscall sur ce qui nécessite un syscall entièrement différent - la paire socket (2) et connect (2). Même cela ne fonctionne pas:

$ nc -U  < /tmp/mysocket.sock
bash: /tmp/mysocket.sock: No such device or address

Mais si nous sommes conscients du type de fichier et de la façon dont nous pouvons appeler le bon syscall, nous pouvons obtenir le comportement souhaité:

$ nc -U /tmp/mysocket.sock
hello, I am testfile.txt and this is first line
line two
line three

last line

Notes et autres lectures suggérées:

6