Zaimplementuj podzbiór skryptu powłoki

12

Ta strona miała wiele problemów związanych z implementacją różnych języków w tagu . Jednak praktycznie wszystkie z nich były językami ezoterycznymi, których nikt nie używa. Czas na tłumacza praktycznego języka, który prawdopodobnie zna już większość użytkowników. Tak, to skrypt powłoki na wypadek problemów z odczytaniem tytułu (nie że tak). (tak, celowo podjąłem to wyzwanie, ponieważ nudzę się takimi językami jak GolfScript i Befunge, które wygrywają wszystko, więc stawiam wyzwanie, gdy bardziej praktyczny język programowania ma większe szanse na wygraną)

Jednak skrypt powłoki jest stosunkowo dużym językiem, więc nie będę prosił o jego implementację. Zamiast tego stworzę mały podzbiór funkcji skryptu powłoki.

Podzbiór, który zdecydowałem, to następujący podzbiór:

  • Wykonywanie programów (programy będą zawierać tylko litery, nawet jeśli dozwolone są pojedyncze cudzysłowy)
  • Argumenty programowe
  • Pojedyncze cudzysłowy (akceptujący dowolny znak ASCII do wydruku, w tym spacje, z wyłączeniem pojedynczego cudzysłowu)
  • Niecytowane ciągi znaków (zezwalające na litery, cyfry i myślniki ASCII)
  • Rury
  • Puste wyciągi
  • Wiele instrukcji oddzielonych nowym wierszem
  • Spacje końcowe / wiodące / wiele spacji

W tym zadaniu musisz odczytać dane wejściowe ze STDIN i uruchomić każdą żądaną komendę. Możesz bezpiecznie założyć system operacyjny zgodny z POSIX, więc nie ma potrzeby przenośności z Windows, ani nic podobnego. Możesz bezpiecznie założyć, że programy, które nie są potokowane do innych programów, nie będą czytać ze STDIN. Możesz bezpiecznie założyć, że polecenia będą istnieć. Możesz bezpiecznie założyć, że nic więcej nie zostanie użyte. Jeśli jakieś bezpieczne założenie zostanie złamane, możesz zrobić wszystko. Możesz bezpiecznie założyć maksymalnie 15 argumentów i wiersze poniżej 512 znaków (jeśli potrzebujesz jawnego przydziału pamięci lub coś takiego - naprawdę dam małe szanse na wygraną dla C, nawet jeśli nadal są małe). Nie musisz czyścić deskryptorów plików.

Możesz wykonywać programy w dowolnym momencie - nawet po otrzymaniu pełnej linii lub po zakończeniu STDIN. Wybierz dowolne podejście.

Prosta walizka testowa, która pozwala przetestować powłokę (zwróć uwagę na końcowe białe znaki po trzecim poleceniu):

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

Powyższy program powinien wypisać następujący wynik:

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

Nie możesz wykonać samej powłoki, chyba że nie masz żadnych argumentów dla polecenia (ten wyjątek został stworzony dla Perla, który uruchamia polecenie w powłoce po umieszczeniu tylko argumentu system, ale możesz nadużywać tego wyjątku dla innych języki, jeśli możesz to zrobić w sposób, który oszczędza znaki), lub uruchomione polecenie jest samą powłoką. Jest to prawdopodobnie największy problem w tym wyzwaniu, ponieważ wiele języków ma systemfunkcje wykonujące powłokę. Zamiast tego używaj interfejsów API języka, które wywołują programy bezpośrednio, takich jak subprocessmoduł w Pythonie. To i tak jest dobry pomysł na bezpieczeństwo, a cóż, nie chciałbyś stworzyć niepewnej powłoki, prawda? To najprawdopodobniej zatrzymuje PHP, ale i tak można wybrać inne języki.

Jeśli masz zamiar zrobić swój program w skrypcie powłoki, nie wolno używać eval, sourcealbo .(jak w, funkcji, a nie znaków). Moim zdaniem wyzwanie byłoby zbyt łatwe.

Dopuszczalne sprytne nadużywanie reguł. Jest wiele rzeczy, których wyraźnie zabroniłem, ale jestem prawie pewien, że nadal możesz robić rzeczy, o których nie myślałem. Czasami jestem zaskoczony tym, jak ludzie interpretują moje zasady. Pamiętaj też, że możesz zrobić wszystko dla czegoś, o czym nie wspomniałem. Na przykład, jeśli spróbuję użyć zmiennych, możesz wyczyścić dysk twardy (ale nie rób tego).

Wygrywa najkrótszy kod, ponieważ jest to codegolf.

Konrad Borowski
źródło
Rury ... Dlaczego to musiały być rury ...
JB
1
@JB: Skrypt powłoki bez potoków nie jest moim zdaniem skryptem powłoki, ponieważ przepływ kodu w powłoce UNIX oparty jest na potokach.
Konrad Borowski
Zgadzam się. Nadal uważam, że jest to najbardziej bolesna część wyzwania, które należy podjąć.
JB
@JB Zgadzam się; Pomijam ten.
Timtech
4
Miałem na myśli, że całkowicie pomijam to wyzwanie.
Timtech

Odpowiedzi:

7

Bash (92 bajty)

Korzystając z tej samej luki co ta odpowiedź , oto znacznie krótsze rozwiązanie:

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

Python ( 247 241 239 bajtów)

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
źródło
To wygląda świetnie. Istnieje kilka optymalizacji, które można wykonać (np. Wcześniej usunąć białe znaki *), ale poza tym wygląda świetnie :-). Dziwi mnie, że nowy członek tak dobrze rozwiązał trudny problem.
Konrad Borowski
@xfix Wielkie dzięki! Naprawdę podobało mi się to wyzwanie :-)
tecywiz121
10

C (340 bajtów)

Nie mam żadnego doświadczenia w golfie, ale musisz gdzieś zacząć, więc oto:

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

Dodałem podział wiersza, abyś nie musiał przewijać, ale nie uwzględniłem ich w mojej liczbie, ponieważ nie mają one znaczenia semantycznego. Te po dyrektywach preprocesora są wymagane i zostały policzone.

Wersja bez golfa

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

funkcje

  • Równoległe wykonywanie: możesz wpisać następne polecenie, gdy poprzednie jest wciąż wykonywane.
  • Kontynuacja potoków: możesz wprowadzić nowy wiersz po znaku potoku i kontynuować polecenie w następnym wierszu.
  • Prawidłowe postępowanie z sąsiednimi słowami / ciągami: rzeczy takie 'ec'ho He'll''o 'worldjak powinny działać tak, jak powinny. Być może kod byłby prostszy bez tej funkcji, dlatego z przyjemnością wyjaśnię, czy jest to wymagane.

Znane problemy

  • Połowa deskryptorów plików nigdy nie jest zamknięta, procesy potomne nigdy nie są zbierane. W dłuższej perspektywie prawdopodobnie spowoduje to pewne wyczerpanie zasobów.
  • Jeśli program próbuje odczytać dane wejściowe, zachowanie jest niezdefiniowane, ponieważ moja powłoka odczytuje dane wejściowe z tego samego źródła w tym samym czasie.
  • Wszystko może się zdarzyć, jeśli execvpwywołanie nie powiedzie się, np. Z powodu błędnie wpisanej nazwy programu. Następnie mamy dwa procesy grające jednocześnie w powłoki.
  • Znaki specjalne „|” i łamanie linii zachowuje swoje specjalne znaczenie w cytowanych ciągach. Jest to niezgodne z wymogami, więc badam sposoby rozwiązania tego problemu. Naprawiono, kosztem około 11 bajtów.

Inne notatki

  • Rzecz oczywiście nie zawiera pojedynczego nagłówka, więc zależy to od niejawnych deklaracji wszystkich używanych funkcji. W zależności od konwencji wywoływania może to stanowić problem.
  • Początkowo miałem błąd, w którym echo 'foo bar baz' | sed 's/bar/BAR/' | sed 's/baz/zap/'się zawiesiłem. Problemem najwyraźniej był niezamknięty potok zapisu, więc musiałem dodać polecenie close, które zwiększyło mój kod o 10 bajtów. Być może istnieją systemy, w których taka sytuacja nie występuje, więc mój kod może zostać oceniony z 10 bajtami mniej. Nie wiem
  • Dzięki końcówek C golfa , w szczególności bez typ zwracany przez główny , EOF obsługi i operator trójskładnikowych , ostatni za wskazanie, że ?:może mieć zagnieżdżone ,bez (…).
MvG
źródło
Możesz wyjść na int c, m, f[3];zewnątrz main, aby uniknąć deklarowania typów. W przypadku zmiennych globalnych nie musisz deklarować int. Ale ogólnie ciekawe rozwiązanie.
Konrad Borowski
zabawa z fork () w systemie Windows. heh
To nie działa dla mnie. Polecenia bez wyjścia potoku dwa razy i działają yes|head -3wiecznie, a powłoka wychodzi po każdym poleceniu. Używam gcc w wersji 4.6.3 (Ubuntu / Linaro 4.6.3-1ubuntu5) bez żadnych przełączników.
Dennis
@Dennis: Dzięki za raport. Niepoprawne użycie operatora trójskładnikowego. Powinienem był przeprowadzić testy jednostkowe przed wklejeniem, ale byłem tego pewien… Naprawiono teraz, kosztem jeszcze jednego bajtu.
MvG
Teraz działa dobrze. Myślę, że możesz usunąć 4 kolejne bajty: 2, definiując makro #define B break;case( break;wcześniej defaultstaje się )B-1:) i 2, zastępując case'\n'i case'\'') przez case 10i case 39.
Dennis
3

bash (+ ekran) 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'

Wyprowadzi coś takiego:

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
źródło
To wywołuje bash w moim systemie, co nie wydaje mi się dozwolone
tecywiz121
Oczywiście, ale po ponownym przeczytaniu pytania myślę, że nie łamie to żadnej reguły (bez systemu, bez argumentów, bez ewaluacji, źródła lub kropki ...)
F. Hauri
Tak, ale w ciekawy sposób: używając odłączonej i niewidocznej sesji, aby wykonać całą pracę, niż przed wyjściem zrzuć całą historię na początkową konsolę.
F. Hauri
Nie mam nic przeciwko nadużywaniu zasad. Moim zdaniem jest to dość sprytne - a pytanie pozwala na sprytne nadużycie zasad. +1 ode mnie
Konrad Borowski
1

Współczynnik (208 znaków)

Ponieważ reguły nie zabraniają przekazywania pracy osobom trzecim ( http://www.compileonline.com/execute_bash_online.php ), oto rozwiązanie:

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 ;

Możesz napisać program jako jeszcze krótszy jeden wiersz w replice ( 201 znaków):

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
źródło
Chyba nie powinienem był dopuszczać się nadużyć. No tak, zrobiłem. +1 ode mnie - nigdy bym o tym nie pomyślał.
Konrad Borowski
0

Perl, 135 znaków

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

Ta skorupa robi głupie rzeczy. Uruchom interaktywną powłokę za pomocą perl shell.pli spróbuj:

  • lswypisuje w jednej kolumnie, ponieważ standardowe wyjście nie jest terminalem. Powłoka przekierowuje standardowe wyjście do potoku i odczytuje z potoku.
  • perl -E 'say "hi"; sleep 1' czeka 1 sekundę na przywitanie, ponieważ powłoka opóźnia wyjście.
  • ddodczytuje 0 bajtów, chyba że jest to pierwsze polecenie tej powłoki. Powłoka przekierowuje standardowe wejście z pustej potoku dla każdego potoku po pierwszym.
  • perl -e '$0 = screamer; print "A" x 1000000' | dd of=/dev/null kończy się pomyślnie.
  • perl -e '$0 = screamer; print "A" x 1000000' | cat | dd of=/dev/null wisi muszla!
    • Błąd nr 1: Powłoka głupio czeka na pierwsze polecenie przed uruchomieniem trzeciego polecenia w tym samym potoku. Gdy rury są pełne, skorupa wchodzi w impas. Tutaj skorupa nie zaczyna się dd, dopóki screamer nie wyjdzie, ale screamer czeka na kota, a kot czeka na powłokę. Jeśli zabijesz screamera (być może z pkill -f screamerinną powłoką), to powłoka zostanie wznowiona.
  • perl -e 'fork and exit; $0 = sleeper; sleep' wisi muszla!
    • Błąd nr 2: Powłoka czeka na ostatnie polecenie w potoku, aby zamknąć potok wyjściowy. Jeśli polecenie zakończy się bez zamykania potoku, powłoka nadal czeka. Jeśli zabijesz podkład, wtedy skorupa wznawia się.
  • 'echo $((2+3))'uruchamia polecenie w / bin / sh. Jest to zachowanie systemu wykonawczego i systemu Perla z jednym argumentem, ale tylko wtedy, gdy argument zawiera znaki specjalne.

Wersja bez golfa

#!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
źródło