[shell] Passer stdin à une commande passé au shell sur stdin

Passer stdin à une commande passé au shell sur stdin [shell] - Shell/Batch - Programmation

Marsh Posté le 02-06-2008 à 13:44:58    

Je voudrait savoir si vous voyez un moyen de passer des données sur l'entrée standard d'une commande, sachant que la commande en question est passée au shell via l'entrée standard du shell. Par exemple considérons la commande suivante :

cat > myfile


Evidemment comma ça elle ne fait pas grand chose, elle est faite pour lire des données sur stdin et les mettre dans un fichier. Elle pourrait par exemple être utilisée comme ça (oui je sais ça parrait bizarre, mais il y a une bonne raison pour laquelle je veux faire ce genre de choses) :

echo "my data" | cat > myfile


Pas de problèmes... Maintenant, imaginer que la commande n'est pas tappée en live dans un shell, ni écrite dans un fichier pour être exécutée, ni passée au shell avec -c, mais qu'elle est passée au shell sur stdin, comme ça :

echo "cat > myfile" | sh


Et là paf, problème : comme le stdin du shell est utilisé pour passer la commande à exécuter, je ne peux plus l'utiliser pour passer ls données qui doivent être consommées par le cat.

 

Donc voilà, je cherche un moyen de passer des données à l'entrée standard d'une commande, quand la commande elle même est passée au shell via l'entrée standard. J'imagine vaguement qu'on peut faire quelque chose en passant des données au shell sur le file descriptor 3 par exemple, et faire en sorte que la commande prennent son entrée standard sur le fd 3... Mais en pratique je ne vois pas trop comment faire.


Message édité par matafan le 02-06-2008 à 13:46:27
Reply

Marsh Posté le 02-06-2008 à 13:44:58   

Reply

Marsh Posté le 02-06-2008 à 13:48:38    

J'pige pas bien ce que tu essaies de faire en fait.  [:croquignol]

Reply

Marsh Posté le 02-06-2008 à 13:58:03    

C'est ce que je craignais :D Faudra que j'explique exactement ce que je fais.

Reply

Marsh Posté le 02-06-2008 à 16:52:23    

Tu veux rester en bash ou le C ca te va ?

Reply

Marsh Posté le 02-06-2008 à 16:57:35    

xargs ?


Message édité par Taz le 02-06-2008 à 16:57:57
Reply

Marsh Posté le 03-06-2008 à 09:46:19    

En fait voilà : je fais un programme en C qui doit générer des lignes de commandes à partir de règles qui se trouvent dans un fichier xml, et les faire exécuter au shell. Pour ce faire j'ai décidé de passer la ligne de commande sur l'entrée standard du shell, parce que c'est la solution la plus robuste. Les autres solutions envisageables, mais moins bonnes, sont :
 

  • Passer la ligne de commande au shell avec -c (/usr/bin/ksh -c "ma ligne de commande" ). Ca pose de gros problèmes dans le cas général car si la ligne de commande contient des choses un peu spéciales, genre double quotes, simple quotes, caractères "--", etc... Ca risque de ne pas marcher.
  • Ecrire la commande dans un fichier temporaire, puis demander au shell d'exécuter le fichier (/usr/bin/ksh tmp_file). Mais les fichiers temporaires, ce n'est jamais génial.


Donc, j'ai décidé de passer la ligne de commande sur l'entrée standard du shell : pipe, dup2, fork, exec("/usr/bin/ksh" ), puis j'écris la commande dans le pipe.
 
Seulement voilà, parfois, j'ai besoin d'envoyer des données sur l'entrée standard de ma ligne de commande. Si j'utilisais "ksh -c" ou "ksh tmp_file" je n'aurais pas de problème, il suffirait d'envoyer les données sur l'entrée standard du shell, et elles seraient consommées par la ligne de commande. Mais avec la solution retenues ce n'est pas possible, car c'est le shell lui même qui consomme données de son entrée standard, pour les interpréter.
 
Au final, ma solution bancale c'est de continuer à envoyer ma ligne de commande sur l'entrée standard du shell quand je n'ai pas besoin d'envoyer de données à l'entrée standard de la ligne de commande. Par contre, quand j'ai besoin d'envoyer des données à l'entrée standard de ma ligne de commande, je prendrais la solution du fichier temporaire, et j'enverrais les données sur l'entrée standard du shell.

Reply

Marsh Posté le 03-06-2008 à 09:49:25    

OK mais bon comme tu dis, vu que la solution choisie est bidon, c'est forcément bancale ensuite. Question à 1000PO: t'as vraiment besoin de passer toutes tes commandes dans un shell et dans le même ?

Reply

Marsh Posté le 03-06-2008 à 09:57:24    

Non, chaque ligne de commande est exécutée dans un nouveau shell.

Reply

Marsh Posté le 03-06-2008 à 10:01:56    

bah alors tu t'en fous du shell, il sert à rien, exécute directement la commande.

Reply

Marsh Posté le 03-06-2008 à 10:16:23    

Taz a écrit :

bah alors tu t'en fous du shell, il sert à rien, exécute directement la commande.


 
 [:plusun] Je ne comprend pas l'interet d'un nouveau shell sauf peut être si tu as des variables d'environement dans tes programmes.

Reply

Marsh Posté le 03-06-2008 à 10:16:23   

Reply

Marsh Posté le 03-06-2008 à 10:22:48    

bah ça change rien, l'environnement suit toujours, shell ou pas.

Reply

Marsh Posté le 03-06-2008 à 10:30:25    

Taz a écrit :

bah ça change rien, l'environnement suit toujours, shell ou pas.


 
Je pensait plutot a un conflit entre les applications qui utilisent les memes variables d'environement.

Reply

Marsh Posté le 03-06-2008 à 10:35:25    

bah l'environnement s'hérite, les modifications ne se propagent pas de fils en père.

Reply

Marsh Posté le 03-06-2008 à 11:25:14    

J'ai besoin d'exécuter un shell car mes lignes de commandes sont des expressions shells. Elles peuvent contenir des redirections, des constructions complexes... Que je ne peux pas interpréter moi même. Donc il faut que je les fasse exécuter par un shell.

Reply

Marsh Posté le 03-06-2008 à 11:35:22    

Ah ah magnifique, j'ai une solution assez intéressante :D
 
Je passe ma ligne de commande sur le stdin du shell, comme d'hab. Par contre, les données que je veux envoyer à ma ligne de commande, je les passe au shell sur le file descriptor 42. Ensuite en interne il suffit que j'entoure la ligne de commande avec "{ ligne_de_commande; } <&42" et le tour est joué, ma ligne de commande lit les données qui arrivent sur le fd 42. Plus crade tu meurs :lol:
 
Edit : <&42, pas <42


Message édité par matafan le 03-06-2008 à 11:43:53
Reply

Marsh Posté le 03-06-2008 à 11:41:40    

J'allais justement te proposer une solution de ce type.
Ca ne me semble pas crade du tout, mais la maniere logique de proceder dans ce contexte: puisque les fd standards ne sont pas dispo (a cause de ton echo), en creer d'autres et les utiliser.
A+,


Message édité par gilou le 03-06-2008 à 11:42:17

---------------
There's more than what can be linked! --    Iyashikei Anime Forever!    --  AngularJS c'est un framework d'engulé!  --
Reply

Marsh Posté le 03-06-2008 à 11:44:27    

Oui mais bon utiliser un numéro de fd hardcodé (42) c'est pas super.

Reply

Marsh Posté le 03-06-2008 à 12:40:30    

Pourquoi les fichiers temporaires c'est pas génial au fait ?

Reply

Marsh Posté le 03-06-2008 à 13:11:25    

Les fichiers temporaires c'est pas super niveau perf je pense. Mon truc doit exécuter des tas de commandes en raffale. La sécurité c'est pas forcément génial non plus, quoi qu'avec mktemp et des droits très restrictifs ça doit passer.
 
Enfin je pense qu'au final je vais prendre la solution du fd 42, sauf que j'étais un peu bête... Pas besoin de hardcoder un fd à utiliser, il suffit de générer la chaine "<&n" avec comme n le file descriptor du coté lecture du pipe.
 
Pour ceux que ça intéresse j'ai codé un petit programme de test qui montre comment ça marche. Le premier argument est la ligne de commande, le deuxième argument est la chaine qu'on veut envoyer à la ligne de commande :
 

Code :
  1. #include <sys/types.h>
  2. #include <sys/wait.h>
  3. #include <stdio.h>
  4. #include <stdlib.h>
  5. #include <string.h>
  6. #include <unistd.h>
  7. int
  8. main(int argc, char *argv[])
  9. {
  10.         int cmdfd[2], datafd[2];
  11.         char *cmd, *data = NULL;
  12.         pid_t pid;
  13.         if (argc < 2 || argc > 3 ) {
  14.                 fputs("Usage: shexec Command [Data]\n", stderr);
  15.                 exit(1);
  16.         }
  17.         cmd = argv[1];
  18.         if (argc >= 3) {
  19.                 data = argv[2];
  20.         }
  21.         /* Create pipes for command and data */
  22.         if (pipe(cmdfd) || pipe(datafd)) {
  23.                 perror("pipe()" );
  24.                 exit(1);
  25.         }
  26.         pid = fork();
  27.         if (pid == -1) {
  28.                 perror("fork()" );
  29.                 exit(1);
  30.         }
  31.         if (pid == 0) {
  32.                 /*** Child ***/
  33.                 /* Close "write" side of pipes */
  34.                 close(cmdfd[1]);
  35.                 close(datafd[1]);
  36.                 /* Commands are read on stdin */
  37.                 if (-1 == dup2(cmdfd[0], 0)) {
  38.                         perror("dup2()" );
  39.                         exit(1);
  40.                 }
  41.                 /* Execute shell */
  42.                 execl("/bin/sh", "sh", (char *)NULL);
  43.                 perror("execl()" );
  44.                 exit(1);
  45.        
  46.         } else {
  47.                 int status;
  48.                 char buf[1024];
  49.                 /*** Parent ***/
  50.                 /* Close "read" side of pipes */
  51.                 close(cmdfd[0]);
  52.                 close(datafd[0]);
  53.                 /* Connect command line's stdin to data pipe */
  54.                 snprintf(buf, sizeof buf,  "{ %s; }<&%d", cmd, datafd[0]);
  55.                 buf[sizeof buf - 1] = '\0';
  56.                 printf("      Parent: %d\n", getpid());
  57.                 printf("       Child: %d\n", pid);
  58.                 printf("     Command: %s\n", buf);
  59.                 printf("        Data: %s\n", data);
  60.                 /* Send command line */
  61.                 write(cmdfd[1], buf, strlen(buf));
  62.                 /* Send data */
  63.                 if (data) {
  64.                         write(datafd[1], data, strlen(data));
  65.                 }
  66.                 close(cmdfd[1]);
  67.                 close(datafd[1]);
  68.                 wait(&status);
  69.                 printf("Child status: 0x%08x\n", status);
  70.                 exit(0);
  71.         }
  72.         return 0;
  73. }


 
A l'exécution ça donne par exemple :

$ ./shexec "cat > out" "How you doin'"
      Parent: 27108
       Child: 27109
     Command: { cat > out; }<&6
        Data: How you doin'
Child status: 0x00000000
$ cat out
How you doin'


Et ça marche parfaitement :sol:


Message édité par matafan le 03-06-2008 à 13:16:40
Reply

Marsh Posté le 03-06-2008 à 13:32:13    

le problème c'est que tu squatte un descripteur.
 
Les fichiers temporaires:
- niveau sécurité, y a pas vraiment de problème avec les bons droits. Ou créer tes fichiers dans un dossier spécifique
- niveau perf: optimisation prématurée. Est-ce que tu as un problèmes de perf ? Avec un FS moderne, ton fichier temporaire, il ne touchera jamais le disque, il restera en cache.
 
En solution intermédiaire, tu as les tubes nommés.
 
Ton code est pas top puisque tu ne vérifies pas le retour des write.

Reply

Marsh Posté le 03-06-2008 à 14:31:41    

Mon code c'est un programme de test pour valider l'idée, il vérifie déjà bien trop de choses ;)

Reply

Marsh Posté le 03-06-2008 à 15:48:09    

snprintf met le 0 final toute seule.

Reply

Marsh Posté le 03-06-2008 à 16:59:36    

Ah oui tient, j'ai confondu avec strncpy.

Reply

Marsh Posté le    

Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed