xargs et vi - «L'entrée ne provient pas d'un terminal»

14

J'ai environ 10 php.inifichiers sur mon système, situés un peu partout, et je voulais les parcourir rapidement. J'ai essayé cette commande:

locate php.ini | xargs vi

Mais vim'avertit Input is not from a terminal, puis la console commence à devenir vraiment bizarre - après quoi je dois appuyer sur :q!pour quitter vi, puis me déconnecter de la session ssh et se reconnecter pour que la console se comporte à nouveau normalement.

Je pense que je comprends en quelque sorte ce qui se passe ici - fondamentalement, la commande n'est pas terminée au vidémarrage, donc la commande n'est peut-être pas terminée et vine pense pas que le terminal est en mode normal.

Je n'ai aucune idée de comment y remédier. J'ai cherché sur Google et aussi unix.stackexchange.com avec malchance.

cwd
la source
En remarque, vous pouvez exécuter resetpour réinitialiser votre terminal lorsqu'il est vissé (vous n'avez pas à vous déconnecter de la session ssh).
wisbucky

Réponses:

12
vi $(locate php.ini)

Remarque: cela posera des problèmes si vos chemins de fichier ont des espaces, mais cela est fonctionnellement équivalent à votre commande.
Cette prochaine version gérera correctement les espaces mais est un peu plus compliquée (les sauts de ligne dans les noms de fichiers le casseront quand même)

(IFS=$'\n'; vi $(locate php.ini))


Explication:

Ce qui se passe, c'est que les programmes héritent de leurs descripteurs de fichiers du processus qui les a engendrés. xargsa son STDIN connecté au STDOUT de locate, donc vin'a aucune idée de ce que le STDIN original contient vraiment.

Patrick
la source
2
xargs est merveilleux, l'un de mes outils préférés - il n'est tout simplement pas adapté à une utilisation avec des programmes qui utilisent stdin pour autre chose qu'un flux de données. j'aime votre réponse et votre explication à part ça, alors +1 quand même :)
cas
@CraigSanders Je n'aime pas ça parce qu'il est trop facile d'en abuser (utiliser de manière incorrecte) et de finir par casser. Je n'ai jamais rencontré quoi que ce soit que j'aie absolument dû utiliser xargscar cela ne pouvait pas être fait directement avec le shell (ou find). Cependant, je peux penser à des cas où ce serait la meilleure solution. Donc, tant que vous comprenez ce qui xargsse passe, comment il répartit les arguments, comment il exécute le programme, etc., et que vous l'utilisez correctement, je dirais:
Patrick
il ne peut pas être battu pour des choses comme ... | awk '{print $3}' | xargs | sed -e 's/ /+/g' | bc(pour additionner toutes les valeurs du champ 3). ou avec sed -e 's/ /|/g'pour construire une expression rationnelle. et oui, comme tout outil, vous devez savoir comment l'utiliser et quelles sont ses limites et mises en garde.
cas
L' vi $(...)approche a également un problème avec les caractères génériques dans les coquilles autres que zsh.
Stéphane Chazelas
Notez également qu'avec l' xargsapproche à côté du problème des espaces, les noms de fichiers avec des guillemets simples, des guillemets doubles et des barres obliques inverses sont également un problème.
Stéphane Chazelas
10

Cette question a déjà été posée sur le forum Super User .

Citant la réponse de @ grawity sur cette question:

Lorsque vous appelez un programme via xargs, le stdin (entrée standard) du programme pointe vers / dev / null. (Comme xargs ne connaît pas le stdin d'origine, il fait la meilleure chose suivante.)

Vim s'attend à ce que son stdin soit le même que son terminal de contrôle et effectue directement divers ioctl liés au terminal sur stdin. Lorsqu'elles sont effectuées sur / dev / null (ou tout autre descripteur de fichier non tty), ces ioctls n'ont aucun sens et renvoient ENOTTY, qui est ignoré en silence.

Ceci est mentionné dans les pages de manuel de xarg. Depuis OSX / BSD:

-o Rouvrir stdin en tant que / dev / tty dans le processus enfant avant d'exécuter la commande. Ceci est utile si vous souhaitez que xargs exécute une application interactive.

Par conséquent, sur OSX, vous pouvez utiliser la commande suivante:

find . -name "php.ini" | xargs -o vim

Bien qu'il n'y ait pas de commutateur direct sur la version GNU, cette commande fonctionnera. (Assurez-vous d'inclure la dummychaîne, sinon elle supprimera le premier fichier.)

find . -name "php.ini" | xargs bash -c '</dev/tty vim "$@"' dummy

Les solutions ci-dessus sont une gracieuseté de Jaime McGuigan sur SuperUser . Les ajouter ici pour tout futur visiteur recherchant cette erreur sur le site.

darnir
la source
3
+1 merci pour le conseil -o. J'utilise xargs depuis des années et je n'ai jamais remarqué que ... je viens de vérifier la page de manuel de mon système, c'est parce que ce n'est pas une fonctionnalité GNU xargs. La page de manuel fournit xargs sh -c 'emacs "$@" < /dev/tty' emacscar ce qu'ils prétendent être une alternative plus flexible et portable (bien que ce soit assez drôle pour GNU de préférer la portabilité aux fonctionnalités :).
cas
2

Avec GNU findutilset un shell prenant en charge la substitution de processus (ksh, zsh, bash), vous pouvez faire:

xargs -r0a <(locate -0 php.ini) vi

L'idée étant de passer la liste des fichiers via a -a filenameplutôt que stdin. L'utilisation -0garantit que cela fonctionne quels que soient les caractères ou les non-caractères que les noms de fichiers peuvent contenir.

Avec zsh, vous pourriez faire:

vi ${(0)"$(locate -0 php.ini)"}

(où 0est l'indicateur d'extension de paramètre à diviser sur les NUL).

Notez cependant que contrairement à xargs -rcela, il s'exécute toujours visans argument si aucun fichier n'est trouvé.

Stéphane Chazelas
la source
0

Modifier plusieurs php.ini dans le même éditeur?

Essayer: vim -o $(locate php.ini)

Marguerite
la source
0

Cette erreur se produit lorsque vim est appelé et qu'il est connecté à la sortie du pipeline précédent, au lieu du terminal et qu'il reçoit différentes entrées inattendues (comme les NUL). La même chose se produit lorsque vous exécutez:, vim < /dev/nulldonc la resetcommande dans ce cas est utile. Cela s'explique bien par la gravité du superutilisateur .

Sur Unix / OSX, vous pouvez utiliser xargsun -oparamètre, comme:

locate php.ini | xargs -o vim

-oRouvrez stdin en tant que / dev / tty dans le processus enfant avant d'exécuter la commande. Ceci est utile si vous souhaitez que xargs exécute une application interactive.

Sous Linux, essayez la solution de contournement suivante:

locate php.ini | xargs -J% sh -c 'vim < /dev/tty $@'

Vous pouvez également utiliser GNU parallelau lieu de xargspour forcer l'allocation tty, par exemple:

locate php.ini | parallel -X --tty vi

Remarque: parallelsous Unix / OSX ne fonctionnera pas car il a des paramètres différents et ne prend pas en charge tty.

De nombreuses autres commandes populaires fournissent également une allocation pseudo-tty (comme -tdans ssh), alors vérifiez l'aide.

Vous pouvez également utiliser findpour passer les noms de fichiers à modifier, donc pas besoin xargs, utilisez simplement -exec, par exemple:

find /etc -name php.ini -exec vim {} +
Kenorb
la source
0

@ Le IFSpiratage de Patrick n'est nécessaire que pour les coquilles stupides comme bashet zsh. fish fractionne la chaîne sur les sauts de ligne par défaut.

$ vim (locate php.ini)

Et Dieu nous aide tous si un seul d'entre nous a un fichier avec une nouvelle ligne dans son nom. Après 17 ans d'utilisation de Linux, je ne l'ai pas vu une seule fois. Je ne prendrais la peine de prendre en charge les noms de fichiers qu'avec des retours à la ligne pour les scripts qui doivent fonctionner quoi qu'il arrive, mais des scripts comme celui-ci ne fonctionnent probablement pas de manière interactive.

énigmatiquePhysicien
la source
zshse divise par défaut sur SPC, TAB, NL et NUL. Ce qu'il ne fait pas par rapport à, bashc'est d'effectuer un globbing sur le résultat afin que les caractères génériques dans les noms de fichiers ne soient pas un problème. Dans zsh, vous feriez IFS=$'\0'; vi $(locate -0 php.ini)ou comme je l'ai montré dans ma réponse vi ${(0)"$(locate -0 php.ini)"}pour un opérateur de fractionnement explicite. A noter également tcsh'svi "`locate php.ini`"
Stéphane Chazelas
ah, merde. OK ça marche: $ f='not there'<ret>$ ls $f<ret>mais ça ne marche pas: ls echo not there. OK, je dois mettre à jour un peu cela.
enigmaticPhysicist
Ouais, zsh ne fait pas la bonne chose quand tu le fais ls "$(echo test; echo other test)". Seul le poisson fait ce qu'il faut.
enigmaticPhysicist
En supposant que vous vouliez dire la même chose sans les guillemets, ce n'est pas "juste", c'est le fractionnement des lignes, c'est juste un choix différent. zsh se divise par défaut sur les mots (comme tous les autres shells) et peut être invité à se diviser sur des lignes ou sur des NUL, soit via $IFSou via des opérateurs explicites ( fet 0des drapeaux d'extension de paramètres). Pour les noms de fichiers arbitraires, le fractionnement par mot ou le fractionnement par ligne est également incorrect , vous devez fractionner sur NUL ou analyser un codage, ce qui fishne peut pas le faire. Dans zsh, c'est IFS=$'\0'; ls -ld -- $(printf '%s\0' "$file1" "$file2")ouls -ld -- ${(0)"$(printf '%s\0' "$file1" "$file2")"}
Stéphane Chazelas
Meh. Le fractionnement sur les nouvelles lignes est suffisant. Comme le dit la réponse, les sauts de ligne dans les noms de fichiers sont extrêmement rares. Je n'ai littéralement jamais vu cela se produire en 17 ans. Et les nouvelles lignes sont des séparateurs bien plus pratiques que les nuls.
enigmaticPhysicist
0

Un moyen rapide de le faire, supposant que vous pouvez garantir qu'aucun des chemins de fichiers contiennent la CPS, TAB, NL, *, ?, [caractères (également \et {...}dans certains obus) est d'utiliser en arrière-tiques (aka accents graves) pour exécuter une commande avant une autre commande en cours d'exécution.

Par exemple

vi `find / -type f -name 'php.ini'`

La commande contenue dans les back-ticks s'exécutera en premier. La sortie de la commande contenue est ensuite exécutée par la commande indiquée avant les ticks arrière.

Par exemple, dans la ligne ci-dessus, la find / -type f -name 'php.ini'commande s'exécutera en premier, enverra la sortie, puis visera exécutée sur le résultat de split + glob appliqué à cette sortie.

mardi
la source
3
les back-ticks sont trop facilement confondus pour les guillemets simples. utiliser à la $(find ...)place.
cas
1
deviner que cela rompra également les espaces et / ou les nouvelles lignes dans les noms de fichiers?
cwd
C'est ainsi que vous exécutez les commandes shell dans les scripts bash. Je n'ai jamais eu de coupure sur les espaces ou les nouvelles lignes dans mes scripts ou lors de leur utilisation dans une seule ligne. Cependant, je n'ai jamais essayé d'ouvrir plusieurs fichiers en viutilisant cette méthode. Il est tout à fait possible qu'il puisse se casser sur de nouvelles lignes ou de nouveaux espaces, selon la façon dont la vilecture et l'exécution de la sortie sont effectuées.
mardi