Comment ne rien faire pour toujours de manière élégante?

82

J'ai un programme qui produit des informations utiles sur stdoutmais lit également stdin. Je veux rediriger sa sortie standard vers un fichier sans rien fournir en entrée standard. Jusqu'ici, tout va bien: je peux faire:

program > output

et ne fais rien dans le tty.

Cependant, le problème est que je veux le faire en arrière-plan. Si je fais:

program > output &

le programme sera suspendu ("suspendu (entrée tty)").

Si je fais:

program < /dev/null > output &

le programme se termine immédiatement car il atteint EOF.

Il me semble que ce dont j'ai besoin, c'est de pénétrer dans programquelque chose qui ne fait rien pendant une durée indéterminée et qui ne lit pas stdin. Les approches suivantes fonctionnent:

while true; do sleep 100; done | program > output &
mkfifo fifo && cat fifo | program > output &
tail -f /dev/null | program > output &

Cependant, tout cela est très moche. Il doit y avoir un moyen élégant, en utilisant les utilitaires Unix standard, de "ne rien faire, indéfiniment" (pour paraphraser man true). Comment pourrais-je y parvenir? (Mes principaux critères d'élégance sont les suivants: pas de fichiers temporaires, pas de files d'attente trop fréquentes ni de réveils périodiques; pas de services publics exotiques; aussi court que possible.)

a3nm
la source
Essayez su -c 'program | output &' user. Je suis sur le point de poser une question similaire avec la création de tâches en arrière-plan en tant que méthode acceptable pour gérer un "service / démon". J'ai aussi remarqué que je ne pouvais pas rediriger STDERRsans rediriger également STDOUT. La solution où programmeA envoie STDOUTà programmeB STDIN, puis redirige STDERRvers un fichier journal:programA 2> /var/log/programA.log | programB 2> /var/log/programB.log 1> /dev/null
mbrownnyc
peut-être ... su -c 'while true; do true; done | cat > ~/output &' user?
mbrownnyc
quel genre de programme est-ce?
João Portela
João Portela: C'est un programme que j'ai écrit, gitorious.org/irctk
a3nm
Pourquoi ne pas simplement ajouter un commutateur à ce programme que vous avez écrit? Aussi, je suppose que si vous fermez stdin avec 1<&-il va quitter votre programme?
w00t

Réponses:

16

Dans les shells qui les prennent en charge (ksh, zsh, bash4), vous pouvez commencer en programtant que co-processus .

  • ksh: program > output |&
  • zsh, bash:coproc program > output

Cela commence programen arrière-plan avec son entrée redirigée depuis un fichier pipe. L'autre extrémité du tuyau est ouverte à la coque.

Trois avantages de cette approche

  • pas de processus supplémentaire
  • vous pouvez quitter le script quand il programmeurt (utilisez-le waitpour l'attendre)
  • programse terminera (obtiendrez eofson stdin si le shell se ferme).
Stéphane Chazelas
la source
Cela semble fonctionner et semble être une bonne idée! (Pour être juste, j'avais demandé quelque chose à transmettre à ma commande, pas pour une fonctionnalité de shell, mais c'était simplement le problème XY en jeu.) J'envisage d'accepter cette réponse plutôt que celle de @ PT.
a3nm
1
@ a3nm, tail -f /dev/nulln'est pas idéal car il lit toutes les secondes /dev/null(les versions actuelles de GNU tail sur Linux utilisant inotify il y a en fait un bogue ). sleep infou son équivalent plus portable sleep 2147483647sont de meilleures approches pour une commande qui reste là sans rien faire d’IMO (note qui sleepest construite dans quelques shells comme ksh93ou mksh).
Stéphane Chazelas
78

Je ne pense pas que vous allez devenir plus élégant que le

tail -f /dev/null

que vous avez déjà suggéré (en supposant que cela utilise inotify en interne, il ne devrait pas y avoir d’interrogation ni de réveil, de sorte qu’autre chose qu’elle soit étrange, elle devrait être suffisante).

Vous avez besoin d'un utilitaire qui s'exécutera indéfiniment, gardera son stdout ouvert, mais n'écrira rien sur stdout et ne se fermera pas lorsque son stdin sera fermé. Quelque chose comme yesécrit en fait sur stdout. catquittera quand son stdin sera fermé (ou ce que vous redirigerez dedans est fait). Je pense que cela sleep 1000000000dpourrait marcher, mais tailc'est nettement mieux. Ma boîte Debian a une tailfcommande qui raccourcit légèrement.

Sous un angle différent, que diriez-vous de lancer le programme screen?

PT
la source
J'aime tail -f /dev/nullmieux l' approche et la trouve suffisamment élégante, car l'utilisation de la commande correspond assez bien à l'objectif recherché.
jw013
2
De strace tail -f /dev/nullil semble que tailutilise inotifyet que redémarrages se produisent dans des cas comme stupides sudo touch /dev/null. Il est regrettable qu'il ne semble pas y avoir de meilleure solution ... Je me demande quel serait le bon système à utiliser pour mettre en œuvre une meilleure solution.
a3nm
5
@ a3nm Le syscall serait pause, mais il n'est pas exposé directement à une interface shell.
Gilles
PT: Je sais screen, mais il s'agit d'exécuter plusieurs occurrences du programme à partir d'un script shell à des fins de test. L'utilisation screenest donc un peu excessive.
a3nm
3
@sillyMunky Silly Monkey, la réponse de WaelJ est fausse (envoie des zéros infinis à stdin).
PT
48

sleep infinity est la solution la plus claire que je connaisse.

Vous pouvez utiliser infinitycar sleepaccepte un nombre à virgule flottante * , qui peut être décimal , hexadécimal , infini ou NaN , selon man strtod.

* Cela ne fait pas partie de la norme POSIX et n’est donc pas aussi portable tail -f /dev/null. Cependant, il est supporté par GNU coreutils (Linux) et BSD (utilisé sur Mac) (apparemment pas supporté par les versions plus récentes de Mac - voir les commentaires).

Zaz
la source
Haha, c'est vraiment une belle approche. :)
a3nm
@ a3nm: Merci:) semble sleep infinityégalement fonctionner sur BSD et Mac .
Zaz
Quel type de ressources prend un processus de sommeil infini? Juste RAM?
CMCDragonkai
1
Cette réponse prétend qu'il sleep infinityattend 24 jours au maximum; qui a raison
nh2
1
@Zaz J'ai étudié la question en détail maintenant. Il s'avère que vous aviez initialement raison! L' sleeputilitaire n'est pas limité à 24 jours ; ce n'est que le premier appel système qui dort pendant 24 jours, et il en fera plus par la suite. Voir mon commentaire ici: stackoverflow.com/questions/2935183/…
nh2
19
sleep 2147483647 | program > output &

Oui, 2^31-1c'est un nombre fini, et il ne fonctionnera pas éternellement , mais je vous donnerai 1 000 $ lorsque le sommeil aura finalement expiré. (Indice: l'un de nous sera mort d'ici là.)

  • aucun fichier temporaire; vérifier.
  • pas de réveil occupé ou périodique; vérifier
  • pas de services publics exotiques; vérifier.
  • aussi court que possible. Ok, ça pourrait être plus court.
Rob
la source
5
bash: sleep $ ((64 # 1 _____)) | programme> sortie &
Diego Torres Milano
Cela dort depuis 68 ans , cela dort depuis 98 siècles : sleep 2147483647d...
agc Le
9

Vous pouvez créer un binaire qui fait exactement cela avec:

$ echo 'int main(){ pause(); }' > pause.c; make pause
PSkocik
la source
5

Voici une autre suggestion utilisant les utilitaires Unix standard, pour "ne rien faire, indéfiniment" .

sh -c 'kill -STOP $$' | program > output

Cela déclenche un shell qui est immédiatement envoyé SIGSTOP, ce qui suspend le processus. Ceci est utilisé comme "entrée" dans votre programme. Le complément de SIGSTOPis SIGCONT, c’est-à-dire que si vous savez que le shell a le PID 12345, vous pouvez kill -CONT 12345le faire continuer.

Roaima
la source
2

Sous Linux, vous pouvez faire:

read x < /dev/fd/1 | program > output

Sous Linux, l’ouverture de / dev / fd / x, où x est un descripteur de fichier situé à la fin de l’écriture d’un pipe, vous donne la fin de la lecture du tuyau, donc la même chose que sur le stdin du programme. Donc, fondamentalement, readne reviendra jamais, car la seule chose qui peut écrire dans ce canal est elle-même et readne génère rien.

Cela fonctionnera également sur FreeBSD ou Solaris, mais pour une autre raison. Là, en ouvrant / dev / fd / 1, vous obtenez la même ressource que celle que vous attendez sur fd 1 et comme le font la plupart des systèmes, à l’exception de Linux, donc en fin d’écriture. Cependant, sous FreeBSD et Solaris, les tuyaux sont bidirectionnels. Donc tant qu’il programn’écrit pas dans son stdin (aucune application ne le fait), readrien n’est lu de cette direction du tuyau.

Sur les systèmes où les canaux ne sont pas bidirectionnels, il y readaura probablement une erreur lors de la tentative de lecture à partir d'un descripteur de fichier en écriture seule. Notez également que tous les systèmes ne l’ont pas /dev/fd/x.

Stéphane Chazelas
la source
Très agréable! En fait mes tests vous n'avez pas besoin de la xbash; plus loin avec zsh vous pouvez simplement faire readet ça marche (bien que je ne comprenne pas pourquoi!). Cette astuce est-elle spécifique à Linux ou fonctionne-t-il sur tous les systèmes * nix?
a3nm
@ a3nm, si vous le faites readseul, il lira de stdin. Donc, si c'est le terminal, il lira ce que vous tapez jusqu'à ce que vous appuyiez sur Entrée.
Stéphane Chazelas
Bien sûr, je comprends ce que lire fait. Ce que je ne comprends pas, c'est pourquoi la lecture du terminal avec une lecture dans un processus en arrière-plan bloque avec bash mais pas avec zsh.
a3nm
@ a3nm, je ne suis pas sûr de ce que vous voulez dire. Qu'est-ce que vous entendez par vous pouvez simplement faire readet cela fonctionne ?
Stéphane Chazelas
Je dis qu'avec zsh, vous pouvez simplement faire read | program > outputet cela fonctionne de la même manière que ce que vous avez suggéré. (Et je ne comprends pas pourquoi.)
a3nm
0

La read solution de Stéphane Chazelas fonctionne également sur Mac OS X si un fichier de lecture est ouvert /dev/fd/1.

# using bash on Mac OS X
# -bash: /dev/fd/1: Permission denied
read x </dev/fd/1 | cat >/dev/null
echo ${PIPESTATUS[*]}   #  1 0

exec 3<&- 3</dev/fd/1
read x 0<&3 | cat >/dev/null
echo ${PIPESTATUS[*]}   #  0 0

Pour pouvoir tuer tail -f /dev/nulldans un script (avec SIGINT, par exemple), il est nécessaire de mettre en arrière-plan la tailcommande et wait.

#!/bin/bash
# ctrl-c will kill tail and exit script
trap 'trap - INT; kill "$!"; exit' INT
exec tail -f /dev/null & wait $!
utilisateur6915
la source
-2

Rediriger en /dev/zerotant qu'entrée standard!

program < /dev/zero > output &
WaelJ
la source
9
Cela donnerait à son programme un nombre infini de zéro octets ... ce qui, malheureusement, le rendrait occupé.
Jander
1
Ce n'est pas vrai Jander, / dev / zero ne se fermera jamais en maintenant la chaîne de tuyaux ouverte. Cependant, l'affiche indique qu'il ne prend pas de stdin, ainsi aucun zéros ne sera jamais transféré au programme. Ce n'est pas une boucle occupée du tout, c'est une attente pure.
SillyMunky
2
désolé, OP utilise stdin, donc cela effacera son entrée et tirera de / dev / zero. Je devrais lire deux fois la prochaine fois! Si OP n'utilisait pas stdin, ce serait la solution la plus élégante que j'ai jamais vue et ce ne serait pas une attente chargée.
StupideMunky