Implementieren Sie eine Teilmenge des Shell-Skripts

12

Diese Seite hatte viele Probleme mit der Implementierung verschiedener Sprachen in Tags. Praktisch alle waren esoterische Sprachen, die niemand benutzte. Es ist Zeit, einen Dolmetscher für eine praktische Sprache zu entwickeln, die die meisten Benutzer hier wahrscheinlich bereits kennen. Ja, es ist ein Shell-Skript, falls Sie Probleme haben, den Titel zu lesen (nicht das, was Sie haben). (Ja, ich habe diese Herausforderung absichtlich gemeistert, da es mir langweilig ist, dass Sprachen wie GolfScript und Befunge alles gewinnen. Deshalb habe ich einige Herausforderungen gestellt, bei denen eine praktischere Programmiersprache größere Gewinnchancen hat.)

Da das Shell-Skript jedoch eine relativ große Sprache ist, werde ich Sie nicht bitten, es zu implementieren. Stattdessen werde ich einen kleinen Teil der Shell-Skriptfunktionalität erstellen.

Die Untergruppe, für die ich mich entschieden habe, ist die folgende Untergruppe:

  • Ausführen von Programmen (Programme enthalten jedoch nur Buchstaben, auch wenn einfache Anführungszeichen zulässig sind)
  • Programmargumente
  • Einfache Anführungszeichen (akzeptieren alle druckbaren ASCII-Zeichen, einschließlich Leerzeichen, ohne einfache Anführungszeichen)
  • Zeichenfolgen ohne Anführungszeichen (ASCII-Buchstaben, -Zahlen und -Bindestriche sind zulässig)
  • Rohre
  • Leere Anweisungen
  • Mehrere Anweisungen durch eine neue Zeile getrennt
  • Nachgestellte / führende / mehrere Leerzeichen

In dieser Task müssen Sie die Eingabe von STDIN lesen und jeden angeforderten Befehl ausführen. Sie können mit Sicherheit von einem POSIX-kompatiblen Betriebssystem ausgehen, sodass keine Portabilität mit Windows oder Ähnlichem erforderlich ist. Sie können davon ausgehen, dass die Programme, die nicht an andere Programme weitergeleitet werden, nicht aus STDIN lesen. Sie können davon ausgehen, dass die Befehle vorhanden sein werden. Sie können davon ausgehen, dass nichts anderes verwendet wird. Wenn eine sichere Annahme gebrochen ist, können Sie alles tun. Sie können sicher annehmen, dass höchstens 15 Argumente und Zeilen mit weniger als 512 Zeichen vorhanden sind (wenn Sie eine explizite Speicherzuweisung benötigen oder so etwas - ich werde wirklich kleine Gewinnchancen für C geben, auch wenn diese noch klein sind). Sie müssen keine Dateideskriptoren bereinigen.

Sie können Programme jederzeit ausführen - auch nach Erhalt der vollständigen Zeile oder nach Beendigung von STDIN. Wählen Sie einen beliebigen Ansatz.

Einfacher Testfall, mit dem Sie Ihre Shell testen können (beachten Sie das Leerzeichen nach dem dritten Befehl):

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/'

Das obige Programm sollte folgendes Ergebnis ausgeben:

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

Sie dürfen die Shell nicht selbst ausführen, es sei denn, Sie haben keine Argumente für den Befehl (diese Ausnahme wurde für Perl gemacht, das den Befehl in der Shell ausführt, wenn nur ein Argument eingegeben wird system, aber Sie können diese Ausnahme für andere missbrauchen auch Sprachen, wenn Sie dies auf eine Weise tun können, die Zeichen spart), oder der Befehl, den Sie ausführen, ist die Shell selbst. Dies ist wahrscheinlich das größte Problem bei dieser Herausforderung, da viele Sprachen systemFunktionen haben, die Shell ausführen. Verwenden Sie stattdessen Sprach-APIs, die Programme direkt aufrufen, z. B. subprocessModule in Python. Dies ist ohnehin eine gute Idee für die Sicherheit, und Sie möchten keine unsichere Shell erstellen, oder? Dies stoppt höchstwahrscheinlich PHP, aber es gibt trotzdem andere Sprachen zur Auswahl.

Wenn Sie vorhaben , Ihr Programm in Shell - Skript zu machen, sind Sie nicht verwenden dürfen eval, sourceoder .(wie in, eine Funktion, kein Zeichen). Das würde meiner Meinung nach die Herausforderung zu einfach machen.

Cleverer Regelmissbrauch erlaubt. Es gibt viele Dinge, die ich ausdrücklich untersagt habe, aber ich bin mir fast sicher, dass Sie immer noch Dinge tun dürfen, an die ich nicht gedacht habe. Manchmal wundert es mich, wie Leute meine Regeln interpretieren. Denken Sie auch daran, dass Sie alles für alles tun können, was ich nicht erwähnt habe. Wenn ich zum Beispiel versuche, Variablen zu verwenden, können Sie die Festplatte löschen (aber bitte nicht).

Der kürzeste Code gewinnt, da dies Codegolf ist.

Konrad Borowski
quelle
Pfeifen ... Warum mussten es Pfeifen sein ...
JB
1
@JB: Shell-Skript ohne Pipelines ist meiner Meinung nach kein Shell-Skript, da der Code-Fluss in der UNIX-Shell auf Pipes basiert.
Konrad Borowski
Genau. Ich denke immer noch, es ist zweifellos der schmerzhafteste Teil der zu implementierenden Herausforderung.
JB
@JB Ich stimme zu; Ich überspringe diesen.
Timtech
4
Ich meinte, dass ich die Herausforderung insgesamt überspringe.
Timtech

Antworten:

7

Bash (92 Bytes)

Nutzen Sie die gleiche Lücke wie diese Antwort , um eine viel kürzere Lösung zu finden:

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

Python ( 247 241 239 Byte)

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(),
tecywiz121
quelle
Das sieht gut aus. Es gibt einige Optimierungen, die durchgeführt werden können (wie das Entfernen von Leerzeichen zuvor *), aber ansonsten sieht es großartig aus :-). Ich bin überrascht, dass ein neues Mitglied eine so gute Lösung für ein schwieriges Problem gefunden hat.
Konrad Borowski
@ xfix Vielen Dank! Ich habe diese Herausforderung wirklich genossen :-)
tecywiz121
10

C (340 Bytes)

Ich habe überhaupt keine Erfahrung im Golfen, aber Sie müssen irgendwo anfangen, also hier geht's:

#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;}}

Ich habe Zeilenumbrüche hinzugefügt, damit Sie nicht scrollen müssen, habe sie aber nicht in meine Zählung aufgenommen, da sie keine semantische Bedeutung haben. Die nach Präprozessor-Direktiven sind erforderlich und wurden gezählt.

Ungolfed-Version

#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;
  }
}

Eigenschaften

  • Parallele Ausführung: Sie können den nächsten Befehl eingeben, während der vorherige noch ausgeführt wird.
  • Fortsetzung der Pipe: Sie können nach einem Pipe-Zeichen eine neue Zeile eingeben und den Befehl in der nächsten Zeile fortsetzen.
  • Richtiger Umgang mit benachbarten Wörtern / Zeichenketten: Dinge wie 'ec'ho He'll''o 'worldarbeiten, wie sie sollen. Könnte gut sein, dass der Code ohne diese Funktion einfacher gewesen wäre, daher würde ich eine Klarstellung begrüßen, ob dies erforderlich ist.

Bekannte Probleme

  • Die Hälfte der Dateideskriptoren wird nie geschlossen, untergeordnete Prozesse werden nie geerntet. Langfristig wird dies wahrscheinlich zu einer gewissen Ressourcenerschöpfung führen.
  • Wenn ein Programm versucht, Eingaben zu lesen, ist das Verhalten undefiniert, da meine Shell gleichzeitig Eingaben von derselben Quelle liest.
  • Alles kann passieren, wenn der execvpAufruf fehlschlägt, z. B. aufgrund eines falsch eingegebenen Programmnamens. Dann spielen zwei Prozesse gleichzeitig die Rolle der Muschel.
  • Sonderzeichen '|' und Zeilenumbruch behalten ihre besondere Bedeutung innerhalb der in Anführungszeichen gesetzten Zeichenfolgen. Dies verstößt gegen die Anforderungen, daher suche ich nach Möglichkeiten, dies zu beheben. Behoben, bei einem Aufwand von ca. 11 Bytes.

Weitere Hinweise

  • Das Ding enthält offensichtlich keinen einzelnen Header, daher hängt es von impliziten Deklarationen aller verwendeten Funktionen ab. Abhängig von den Aufrufkonventionen kann dies ein Problem sein oder auch nicht.
  • Anfangs hatte ich einen Bug, bei dem echo 'foo bar baz' | sed 's/bar/BAR/' | sed 's/baz/zap/'es hängen blieb. Das Problem war anscheinend die nicht geschlossene Write-Pipe, daher musste ich den Befehl close hinzufügen, der meine Codegröße um 10 Byte erhöhte. Möglicherweise gibt es Systeme, in denen diese Situation nicht auftritt, sodass mein Code möglicherweise mit 10 Byte weniger bewertet wird. Ich weiß es nicht.
  • Dank der C-Golftipps , insbesondere ohne Rückgabetyp für Haupt- , EOF-Handling und Ternäroperator , kann der letzte zum Hinweisen, dass ohne ?:geschachtelt haben kann .,(…)
MvG
quelle
Sie können nach int c, m, f[3];draußen gehen main, um das Deklarieren von Typen zu vermeiden. Für globale Variablen müssen Sie nicht deklarieren int. Aber im Allgemeinen interessante Lösung.
Konrad Borowski
Spaß mit Fork () auf Windows. heh
Das funktioniert bei mir nicht. Befehle ohne Pipe werden zweimal ausgegeben und yes|head -3bleiben für immer bestehen, und die Shell wird nach jedem einzelnen Befehl beendet. Ich verwende die gcc-Version 4.6.3 (Ubuntu / Linaro 4.6.3-1ubuntu5) ohne Schalter.
Dennis
@ Tennis: Danke für den Bericht. Falsche Verwendung des ternären Operators. Ich hätte Unit-Tests durchführen sollen, bevor ich sie eingefügt habe, aber ich war mir so sicher ... Jetzt behoben, auf Kosten eines weiteren Bytes.
MvG
Es funktioniert jetzt gut. Ich denke, Sie können 4 weitere Bytes abscheuern: 2 durch Definieren des Makros #define B break;case(das break;Vorher defaultwird )B-1:) und 2 durch Ersetzen von case'\n'und case'\''durch case 10und case 39.
Dennis
3

Bash (+ Bildschirm) 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'

Gibt etwas aus wie:

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:~$
F. Hauri
quelle
Dies ruft Bash auf meinem System auf, was ich nicht für zulässig
halte
Natürlich, aber nach dem erneuten Lesen der Frage, denke ich, dass dies keine Regel verletzt (kein System, kein Argument, keine Bewertung, Quelle oder Punkt ...)
F. Hauri
Ja, aber auf interessante Weise: Verwenden Sie eine getrennte und unsichtbare Sitzung, um die gesamte Arbeit zu erledigen, und sichern Sie dann vor dem Beenden den gesamten Verlauf auf der ersten Konsole.
F. Hauri
Mir geht es gut mit dieser Regel Missbrauch. Es ist meiner Meinung nach klug genug - und die Frage erlaubt klugen Regelmissbrauch. +1 von mir.
Konrad Borowski
1

Faktor (208 Zeichen)

Da die Regeln das Auslagern der Arbeit an Dritte ( http://www.compileonline.com/execute_bash_online.php ) nicht verbieten , gibt es hier eine Lösung:

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 ;

Sie können das Programm auch als noch kürzeren Einzeiler in die Antwort schreiben ( 201 Zeichen):

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 ;
Björn Lindqvist
quelle
Ich denke, ich hätte keinen Regelmissbrauch zulassen sollen. Oh ja, das habe ich getan. +1 von mir - daran würde ich einfach nie denken.
Konrad Borowski
0

Perl, 135 Zeichen

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

Diese Shell macht einige dumme Sachen. Starten Sie eine interaktive Shell mit perl shell.plund probieren Sie es aus:

  • lsDruckt in einer Spalte, da die Standardausgabe kein Terminal ist. Die Shell leitet die Standardausgabe an eine Pipe um und liest aus der Pipe.
  • perl -E 'say "hi"; sleep 1' Wartet 1 Sekunde, um Hallo zu sagen, da die Shell die Ausgabe verzögert.
  • ddLiest 0 Bytes, es sei denn, es ist der erste Befehl für diese Shell. Die Shell leitet die Standardeingabe von einer leeren Pipe für jede Pipeline nach der ersten Pipe um.
  • perl -e '$0 = screamer; print "A" x 1000000' | dd of=/dev/null erfolgreich abgeschlossen.
  • perl -e '$0 = screamer; print "A" x 1000000' | cat | dd of=/dev/null hängt die Muschel!
    • Fehler Nr. 1: Die Shell wartet dumm auf den ersten Befehl, bevor sie den dritten Befehl in derselben Pipeline startet. Wenn die Rohre voll sind, tritt die Hülle in einen Deadlock ein. Hier startet die Shell nicht dd, bis der Screamer beendet wird, sondern der Screamer wartet auf die Katze und die Katze wartet auf die Shell. Wenn Sie einen Screamer töten (möglicherweise mit einer pkill -f screameranderen Shell), wird die Shell fortgesetzt.
  • perl -e 'fork and exit; $0 = sleeper; sleep' hängt die Muschel!
    • Fehler Nr. 2: Die Shell wartet auf den letzten Befehl in einer Pipeline, um die Ausgabepipe zu schließen. Wenn der Befehl beendet wird, ohne die Pipe zu schließen, wartet die Shell weiter. Wenn Sie den Schläfer töten, wird die Shell fortgesetzt.
  • 'echo $((2+3))'Führt den Befehl in / bin / sh aus. Dies ist das Verhalten von Perls exec und system mit einem Argument, jedoch nur, wenn das Argument Sonderzeichen enthält.

Ungolfed-Version

#!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.
Kernigh
quelle