Implémenter un sous-ensemble de script shell


12

Ce site a eu beaucoup de problèmes lors de l'implémentation de différentes langues dans la balise . Cependant, presque tous étaient des langues ésotériques que personne n'utilisait. Il est temps de faire un interprète pour une langue pratique que la plupart des utilisateurs ici connaissent probablement déjà. Oui, c'est un script shell, au cas où vous auriez des problèmes pour lire le titre (pas que vous ayez). (oui, j'ai intentionnellement fait ce défi, car je m'ennuie de langages comme GolfScript et Befunge qui gagnent tout, donc j'ai mis un défi où un langage de programmation plus pratique a plus de chances de gagner)

Cependant, le script shell est un langage relativement gros, donc je ne vous demanderai pas de l'implémenter. Au lieu de cela, je vais créer un petit sous-ensemble de fonctionnalités de script shell.

Le sous-ensemble sur lequel j'ai décidé est le sous-ensemble suivant:

  • Exécution de programmes (les programmes ne contiendront cependant que des lettres, même si les guillemets simples sont autorisés)
  • Arguments du programme
  • Citations simples (acceptant tout caractère ASCII imprimable, y compris les espaces, à l'exclusion des guillemets simples)
  • Chaînes sans guillemets (autorisant les lettres, chiffres et tirets ASCII)
  • Pipes
  • Déclarations vides
  • Plusieurs instructions séparées par une nouvelle ligne
  • Trailing / leader / espaces multiples

Dans cette tâche, vous devez lire l'entrée de STDIN et exécuter chaque commande demandée. Vous pouvez supposer en toute sécurité un système d'exploitation compatible POSIX, il n'y a donc pas besoin de portabilité avec Windows, ou quelque chose comme ça. Vous pouvez supposer en toute sécurité que les programmes qui ne sont pas redirigés vers d'autres programmes ne liront pas depuis STDIN. Vous pouvez supposer en toute sécurité que les commandes existeront. Vous pouvez sans risque supposer que rien d'autre ne sera utilisé. Si une hypothèse sûre est brisée, vous pouvez tout faire. Vous pouvez supposer en toute sécurité au plus 15 arguments et des lignes inférieures à 512 caractères (si vous avez besoin d'une allocation de mémoire explicite, ou quelque chose - je vais vraiment donner de petites chances de gagner pour C, même si elles sont encore petites). Vous n'avez pas à nettoyer les descripteurs de fichiers.

Vous êtes autorisé à exécuter des programmes à tout moment - même après avoir reçu la ligne complète ou après la fin de STDIN. Choisissez n'importe quelle approche que vous souhaitez.

Cas de test simple qui vous permet de tester votre shell (notez les espaces blancs après la troisième commande):

echo hello world
printf '%08X\n' 1234567890
'echo'   'Hello,   world!'  

echo heeeeeeelllo | sed 's/\(.\)\1\+/\1/g'
  yes|head -3
echo '\\'
echo 'foo bar baz' | sed 's/bar/BAR/' | sed 's/baz/zap/'

Le programme ci-dessus devrait produire le résultat suivant:

hello world
499602D2
Hello,   world!
helo
y
y
y
\\
foo BAR zap

Vous n'êtes pas autorisé à exécuter le shell lui-même, à moins que vous n'ayez pas d'arguments pour la commande (cette exception a été faite pour Perl, qui exécute la commande dans le shell lorsque vous placez juste un argument system, mais n'hésitez pas à abuser de cette exception pour d'autres langues aussi, si vous pouvez le faire d'une manière qui enregistre les caractères), ou la commande que vous exécutez est le shell lui-même. C'est probablement le plus gros problème de ce défi, car de nombreux langages ont des systemfonctions qui exécutent le shell. Utilisez plutôt des API de langage qui appellent directement des programmes, comme le subprocessmodule en Python. C'est une bonne idée pour la sécurité de toute façon, et bien, vous ne voudriez pas créer un shell non sécurisé, le voudriez-vous? Cela arrête très probablement PHP, mais il existe quand même d'autres langages.

Si vous allez faire de votre programme dans le script shell, vous n'êtes pas autorisé à utiliser eval, sourceou .(comme dans une fonction, pas un caractère). Cela rendrait le défi trop facile à mon avis.

Abus de règle intelligent autorisé. Il y a beaucoup de choses que j'ai explicitement interdites, mais je suis presque sûr que vous êtes toujours autorisé à faire des choses auxquelles je n'ai pas pensé. Parfois, je suis surpris de la façon dont les gens interprètent mes règles. N'oubliez pas non plus que vous pouvez faire n'importe quoi pour tout ce que je n'ai pas mentionné. Par exemple, si j'essaie d'utiliser des variables, vous pouvez effacer le disque dur (mais ne le faites pas).

Le code le plus court gagne, car il s'agit de codegolf.


Pipes ... Pourquoi ça devait être des pipes ...
JB

1
@JB: Le script shell sans pipelines n'est pas un script shell à mon avis, car le flux de code dans le shell UNIX est basé sur des tubes.
Konrad Borowski

Je suis d'accord. Je pense toujours que c'est de loin la partie la plus douloureuse du défi à mettre en œuvre.
JB

@JB je suis d'accord; Je saute celui-ci.
Timtech

4
Je voulais dire que je sautais complètement le défi.
Timtech

Réponses:


7

Bash (92 octets)

Profitant de la même faille que cette réponse , voici une solution beaucoup plus courte:

curl -s --url 66.155.39.107/execute_new.php -dlang=bash --data-urlencode code@- | cut -c83-

Python ( 247 241 239 octets)

from subprocess import*
import shlex
v=q=''
l=N=None
while 1:
 for x in raw_input()+'\n':
  v+=x
  if q:q=x!="'"
  elif x=="'":q=1
  elif v!='\n'and x in"|\n":
   l=Popen(shlex.split(v[:-1]),0,N,l,PIPE).stdout;v=''
   if x=="\n":print l.read(),

Ça a l'air génial. Il y a quelques optimisations qui peuvent être faites (comme supprimer les espaces avant *), mais à part ça, ça a l'air génial :-). Je suis surpris qu'un nouveau membre ait trouvé une si bonne solution à un problème difficile.
Konrad Borowski

@xfix Merci beaucoup! J'ai vraiment apprécié ce défi :-)
tecywiz121

10

C (340 octets)

Je n'ai aucune expérience du golf, mais vous devez commencer quelque part, alors voici:

#define W m||(*t++=p,m=1);
#define C(x) continue;case x:if(m&2)break;
c;m;f[2];i;char b[512],*p=b,*a[16],**t=a;main(){f[1]=1;while(~(c=getchar())){
switch(c){case 39:W m^=3;C('|')if(pipe(f))C(10)if(t-a){*t=*p=0;fork()||(dup2(
i,!dup2(f[1],1)),execvp(*a,a));f[1]-1&&close(f[1]);i=*f;*f=m=0;f[1]=1;p=b;t=a
;}C(32)m&1?*p++=0,m=0:0;C(0)}W*p++=c;}}

J'ai ajouté des sauts de ligne pour que vous n'ayez pas à faire défiler, mais je ne les ai pas inclus dans mon compte car ils sont sans signification sémantique. Ceux après les directives du préprocesseur sont requis et ont été comptés.

Version non golfée

#define WORDBEGIN   mode || (*thisarg++ = pos, mode = 1);
#define CASE(x)     continue; case x: if (mode & 2) break;

// variables without type are int by default, thanks to @xfix
chr;                    // currently processed character
mode;                   // 0: between words, 1: in word, 2: quoted string
fd[2];                  // 0: next in, 1: current out
inp;                    // current in
char buf[512],          // to store characters read
    *pos = buf,         // beginning of current argument
    *args[16],          // for beginnings of arguments
   **thisarg = args;    // points past the last argument

main() {                          // codegolf.stackexchange.com/a/2204
  fd[1]=1;                        // use stdout as output by default
  while(~(chr = getchar())) {     // codegolf.stackexchange.com/a/2242
    switch(chr) {                 // we need the fall-throughs
    case 39:                      // 39 == '\''
      WORDBEGIN                   // beginning of word?
      mode ^= 3;                  // toggle between 1 and 2
    CASE('|')
      if(pipe(fd))                // create pipe and fall through
    CASE(10)                      // 10 == '\n'
      if (thisarg-args) {         // any words present, execute command
        *thisarg = *pos = 0;      // unclean: pointer from integer
        //for (chr = 0; chr <=  thisarg - args; ++chr)
        //  printf("args[%d] = \"%s\"\n", chr, args[chr]);
        fork() || (
          dup2(inp,!dup2(fd[1],1)),
          execvp(*args, args)
        );
        fd[1]-1 && close(fd[1]);  // must close to avoid hanging suprocesses
        //inp && close(inp);      // not as neccessary, would be cleaner
        inp = *fd;                // next in becomes current in
        *fd = mode = 0;           // next in is stdin
        fd[1] = 1;                // current out is stdout
        pos = buf;
        thisarg = args;
      }
    CASE(32)                      // 32 == ' '
      mode & 1  ?                 // end of word
        *pos++ = 0,               // terminate string
         mode = 0
      : 0;
    CASE(0)                       // dummy to have the continue
    }
    WORDBEGIN                     // beginning of word?
    *pos++ = chr;
  }
}

traits

  • Exécution parallèle: vous pouvez taper la commande suivante pendant que la précédente est toujours en cours d'exécution.
  • Poursuite des tuyaux: vous pouvez entrer une nouvelle ligne après un caractère de tuyau et continuer la commande sur la ligne suivante.
  • Gestion correcte des mots / chaînes adjacents: des choses comme le 'ec'ho He'll''o 'worldtravail comme il se doit. Il se pourrait bien que le code aurait été plus simple sans cette fonctionnalité, je serais donc heureux de savoir si cela est nécessaire.

Problèmes connus

  • La moitié des descripteurs de fichiers ne sont jamais fermés, les processus enfants ne sont jamais récoltés. À long terme, cela entraînera probablement une sorte d'épuisement des ressources.
  • Si un programme essaie de lire l'entrée, le comportement n'est pas défini, car mon shell lit l'entrée de la même source en même temps.
  • Tout peut arriver en cas execvpd'échec de l' appel, par exemple en raison d'un nom de programme mal saisi. Ensuite, nous avons deux processus jouant simultanément sur le shell.
  • Caractères spéciaux '|' et les sauts de ligne conservent leur signification particulière à l'intérieur des chaînes entre guillemets. Ceci est en violation des exigences, j'examine donc les moyens de résoudre ce problème. Fixe, au coût d'environ 11 octets.

Autres notes

  • La chose ne comprend évidemment pas un seul en-tête, donc cela dépend des déclarations implicites de toutes les fonctions utilisées. Selon les conventions d'appel, cela peut ou non être un problème.
  • Au départ, j'avais un bug où se echo 'foo bar baz' | sed 's/bar/BAR/' | sed 's/baz/zap/'bloquait. Le problème était apparemment le tube d'écriture non fermé, j'ai donc dû ajouter cette commande close, ce qui a augmenté la taille de mon code de 10 octets. Il y a peut-être des systèmes où cette situation ne se produit pas, donc mon code peut être évalué avec 10 octets de moins. Je ne sais pas.
  • Grâce aux conseils de golf C , en particulier pas de type de retour pour le principal , la manipulation EOF et l' opérateur ternaire , le dernier à signaler qui ?:peut avoir emboîté ,sans (…).

Vous pouvez vous déplacer à l' int c, m, f[3];extérieur mainpour éviter de déclarer des types. Pour les variables globales, vous n'avez pas à déclarer int. Mais généralement, solution intéressante.
Konrad Borowski

amusez-vous avec fork () sur les fenêtres. heh

Ça ne marche pas pour moi. Les commandes sans sortie de tuyau deux fois et yes|head -3continuent indéfiniment et le shell se ferme après chaque commande. J'utilise la version 4.6.3 de gcc (Ubuntu / Linaro 4.6.3-1ubuntu5) sans aucun commutateur.
Dennis

@Dennis: Merci pour le rapport. Utilisation incorrecte de l'opérateur ternaire. J'aurais dû exécuter des tests unitaires avant de coller, mais j'étais si sûr… Fixé maintenant, au prix d'un octet de plus.
MvG

Cela fonctionne bien maintenant. Je pense que vous pouvez rayer 4 octets de plus: 2 en définissant la macro #define B break;case(l' break;avant defaultdevient )B-1:) et 2 en remplaçant case'\n'et case'\'') par case 10et case 39.
Dennis

3

bash (+ écran) 160

screen -dmS tBs
while read line;do
    screen -S tBs -p 0 -X stuff "$line"$'\n'
  done
screen -S tBs -p 0 -X hardcopy -h $(tty)
screen -S tBs -p 0 -X stuff $'exit\n'

Sortira quelque chose comme:

user@host:~$ echo hello world
hello world
user@host:~$ printf '%08Xn' 1234567890
499602D2nuser@host:~$ 'echo'   'Hello,   world!'
Hello,   world!
user@host:~$
user@host:~$ echo heeeeeeelllo | sed 's/(.)1+/1/g'
yes|head -3
heeeeeeelllo
user@host:~$ yes|head -3
echo ''
y
y
y
user@host:~$ echo ''

user@host:~$ echo 'foo bar baz' | sed 's/bar/BAR/' | sed 's/baz/zap/'
foo BAR zap
user@host:~$

Cela invoque bash sur mon système, ce qui, je pense, n'est pas autorisé
tecywiz121

Bien sûr, mais après relecture de la question, je pense que cela ne déroge à aucune règle (Pas de système, pas d'argument, pas d'eval, de source ou de point ...)
F. Hauri

Oui, mais d'une manière intéressante: utiliser une session détachée et invisible pour faire tout le travail, puis, avant de quitter, vider tout l'historique sur la console initiale.
F.Hauri

Je suis d'accord avec cet abus de règle. C'est assez intelligent à mon avis - et la question permet un abus de règle intelligent. +1 de moi.
Konrad Borowski

1

Facteur (208 caractères)

Étant donné que les règles ne permettent pas de décharger le travail à un tiers ( http://www.compileonline.com/execute_bash_online.php ), voici une solution:

USING: arrays http.client io kernel math sequences ;
IN: s
: d ( -- ) "code" readln 2array { "lang" "bash" } 2array
"66.155.39.107/execute_new.php" http-post*
dup length 6 - 86 swap rot subseq write flush d ;

Vous pouvez également écrire le programme sous forme de ligne unique encore plus courte ( 201 caractères):

USING: arrays http.client io kernel math sequences ; [ "code" swap 2array { "lang" "bash" } 2array "66.155.39.107/execute_new.php" http-post* dup length 6 - 86 swap rot subseq write flush ] each-line ;

Je suppose que je n'aurais pas dû permettre un abus de règle. Oh c'est vrai, je l'ai fait. +1 de moi - je n'y penserais jamais.
Konrad Borowski

0

Perl, 135 caractères

#!perl -n
for(/(?:'.*?'|[^|])+/g){s/'//g for@w=/(?:'.*?'|\S)+/g;open($o=(),'-|')or$i&&open(STDIN,'<&',$i),exec@w,exit;$i=$o}print<$o>

Cette coquille fait des choses stupides. Démarrez un shell interactif avec perl shell.plet essayez-le:

  • lss'imprime dans une colonne, car la sortie standard n'est pas un terminal. Le shell redirige la sortie standard vers un tuyau et lit à partir du tuyau.
  • perl -E 'say "hi"; sleep 1' attend 1 seconde pour dire bonjour, car le shell retarde la sortie.
  • ddlit 0 octet, sauf s'il s'agit de la première commande de ce shell. Le shell redirige l'entrée standard d'un tube vide, pour chaque pipeline après le premier.
  • perl -e '$0 = screamer; print "A" x 1000000' | dd of=/dev/null se termine avec succès.
  • perl -e '$0 = screamer; print "A" x 1000000' | cat | dd of=/dev/null accroche la coquille!
    • Bogue n ° 1: le shell attend bêtement la première commande avant de démarrer la troisième commande dans le même pipeline. Lorsque les tuyaux sont pleins, la coque entre dans une impasse. Ici, le shell ne démarre pas dd jusqu'à la sortie de Screamer, mais Screamer attend Cat et Cat attend le shell. Si vous tuez Screamer (peut-être avec pkill -f screamerun autre shell), le shell reprend.
  • perl -e 'fork and exit; $0 = sleeper; sleep' accroche la coquille!
    • Bogue n ° 2: le shell attend la dernière commande d'un pipeline pour fermer le canal de sortie. Si la commande se termine sans fermer le canal, le shell continue d'attendre. Si vous tuez le dormeur, la coquille reprend.
  • 'echo $((2+3))'exécute la commande dans / bin / sh. C'est le comportement de l' exec et du système de Perl avec un seul argument, mais seulement si l'argument contient des caractères spéciaux.

Version non golfée

#!perl -n
# -n wraps script in while(<>) { ... }

use strict;
our($i, $o, @w);

# For each command in a pipeline:
for (/(?:'.*?'|[^|])+/g) {
    # Split command into words @w, then delete quotes.
    s/'//g for @w = /(?:'.*?'|\S)+/g;

    # Fork.  Open pipe $o from child to parent.
    open($o = (), '-|') or
        # Child redirects standard input, runs command.
        $i && open(STDIN, '<&', $i), exec(@w), exit;

    $i = $o;  # Input of next command is output of this one.
}

print <$o>;   # Print output of last command.
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.