Implementare un sottoinsieme di script di shell


12

Questo sito ha avuto molti problemi con l'implementazione di varie lingue nel tag . Tuttavia, praticamente tutti erano lingue esoteriche che nessuno usa. È ora di creare un interprete per un linguaggio pratico che la maggior parte degli utenti qui probabilmente già conosce. Sì, è lo script della shell, nel caso in cui tu abbia problemi a leggere il titolo (non che tu abbia). (sì, ho intenzionalmente fatto questa sfida, dato che sono annoiato da linguaggi come GolfScript e Befunge che vincono tutto, quindi ho messo una sfida in cui un linguaggio di programmazione più pratico ha maggiori possibilità di vincere)

Tuttavia, lo script della shell è un linguaggio relativamente grande, quindi non ti chiederò di implementarlo. Invece, realizzerò un piccolo sottoinsieme di funzionalità di script di shell.

Il sottoinsieme che ho deciso è il seguente sottoinsieme:

  • Esecuzione di programmi (i programmi conterranno solo lettere, tuttavia, anche se sono consentite virgolette singole)
  • Argomenti del programma
  • Virgolette singole (accettando qualsiasi carattere ASCII stampabile, inclusi spazi bianchi, esclusa virgoletta singola)
  • Stringhe non quotate (che consentono lettere, numeri e trattini ASCII)
  • Pipes
  • Dichiarazioni vuote
  • Istruzioni multiple separate da una nuova riga
  • Trailing / leader / spazi multipli

In questa attività, è necessario leggere l'input da STDIN ed eseguire tutti i comandi richiesti. Puoi assumere in sicurezza un sistema operativo compatibile con POSIX, quindi non è necessaria la portabilità con Windows o qualcosa del genere. Puoi tranquillamente supporre che i programmi che non sono inviati ad altri programmi non leggano da STDIN. Puoi tranquillamente presumere che i comandi esistano. Puoi tranquillamente supporre che non verrà utilizzato nient'altro. Se alcune ipotesi sicure vengono interrotte, puoi fare qualsiasi cosa. Puoi tranquillamente assumere al massimo 15 argomenti e righe inferiori a 512 caratteri (se hai bisogno di un'allocazione esplicita della memoria o qualcosa del genere - darò davvero piccole possibilità di vincere per C, anche se sono ancora piccoli). Non è necessario ripulire i descrittori di file.

È possibile eseguire programmi in qualsiasi momento, anche dopo aver ricevuto la riga completa o al termine di STDIN. Scegli qualsiasi approccio tu voglia.

Testcase semplice che consente di testare la shell (notare lo spazio bianco finale dopo il terzo comando):

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

Il programma sopra dovrebbe produrre il seguente risultato:

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

Non è consentito eseguire la shell stessa, a meno che non si disponga di argomenti per il comando (questa eccezione è stata fatta per Perl, che esegue il comando nella shell quando si inserisce solo l'argomento system, ma sentirsi liberi di abusare di questa eccezione per gli altri anche le lingue, se riesci a farlo in un modo che salva i caratteri) o il comando che esegui è la shell stessa. Questo è probabilmente il problema più grande in questa sfida, poiché molte lingue hanno systemfunzioni che eseguono la shell. Usa invece le API di lingua che richiamano direttamente i programmi, come il subprocessmodulo in Python. Questa è comunque una buona idea per la sicurezza e, bene, non vorresti creare una shell insicura, vero? Molto probabilmente questo interrompe PHP, ma ci sono comunque altre lingue tra cui scegliere.

Se avete intenzione di fare il programma in script di shell, non è consentito di utilizzare eval, sourceo .(come in una funzione, non un carattere). A mio avviso, la sfida sarebbe troppo facile.

Abuso di regole intelligenti consentito. Ci sono molte cose che ho esplicitamente vietato, ma sono quasi sicuro che ti sia ancora permesso di fare cose di cui non ho pensato. A volte sono sorpreso di come le persone interpretano le mie regole. Inoltre, ricorda che puoi fare qualsiasi cosa per tutto ciò che non ho menzionato. Ad esempio, se provo a usare le variabili, puoi cancellare il disco rigido (ma per favore non farlo).

Vince il codice più corto, poiché questo è codegolf.


Tubi ... Perché dovrebbero essere tubi ...
JB

1
@JB: lo script di shell senza pipeline non è script di shell secondo me, poiché il flusso di codice nella shell UNIX si basa su pipe.
Konrad Borowski il

Sono d'accordo. Penso ancora che sia senza dubbio la parte più dolorosa della sfida da attuare.
JB

@JB sono d'accordo; Sto saltando questo.
Timtech,

4
Volevo dire che sto saltando del tutto la sfida.
Timtech,

Risposte:


7

Bash (92 byte)

Sfruttando la stessa scappatoia di questa risposta , ecco una soluzione molto più breve:

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(),

Questo sembra fantastico. Ci sono alcune ottimizzazioni che possono essere fatte (come rimuovere prima gli spazi bianchi *), ma a parte questo, sembra fantastico :-). Sono sorpreso che un nuovo membro abbia fatto una soluzione così valida per un problema difficile.
Konrad Borowski il

@xfix Grazie mille! Mi è davvero piaciuta questa sfida :-)
tecywiz121,

10

C (340 byte)

Non ho alcuna esperienza nel golf, ma devi iniziare da qualche parte, quindi ecco qui:

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

Ho aggiunto le interruzioni di riga in modo da non dover scorrere, ma non le ho incluse nel mio conteggio poiché sono prive di significato semantico. Quelli dopo le direttive del preprocessore sono richiesti e contati.

Versione Ungolfed

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

Caratteristiche

  • Esecuzione parallela: è possibile digitare il comando successivo mentre quello precedente è ancora in esecuzione.
  • Continuazione delle pipe: è possibile inserire una nuova riga dopo un carattere pipe e continuare il comando sulla riga successiva.
  • Gestione corretta di parole / stringhe adiacenti: cose come il 'ec'ho He'll''o 'worldlavoro come dovrebbero. Potrebbe anche essere che il codice sarebbe stato più semplice senza questa funzione, quindi accetterò con favore un chiarimento se questo è necessario.

Problemi noti

  • La metà dei descrittori di file non viene mai chiusa, i processi figlio non vengono mai raccolti. A lungo termine, ciò probabilmente causerà una sorta di esaurimento delle risorse.
  • Se un programma tenta di leggere l'input, il comportamento non è definito, poiché la mia shell legge l'input dalla stessa fonte allo stesso tempo.
  • Tutto può succedere se la execvpchiamata fallisce, ad es. A causa di un nome di programma errato. Quindi abbiamo due processi che giocano contemporaneamente all'essere shell.
  • Caratteri speciali '|' e l'interruzione di linea mantengono il loro significato speciale all'interno delle stringhe tra virgolette. Questo è in violazione dei requisiti, quindi sto studiando come risolverlo. Risolto, con un costo di circa 11 byte.

Altre note

  • La cosa ovviamente non include una singola intestazione, quindi dipende dalle dichiarazioni implicite di tutte le funzioni utilizzate. A seconda delle convenzioni di chiamata, questo potrebbe o non potrebbe essere un problema.
  • Inizialmente avevo un bug in cui echo 'foo bar baz' | sed 's/bar/BAR/' | sed 's/baz/zap/'si bloccava. Apparentemente il problema era la pipe di scrittura non chiusa, quindi ho dovuto aggiungere quel comando di chiusura, che ha aumentato la dimensione del mio codice di 10 byte. Forse ci sono sistemi in cui questa situazione non si presenta, quindi il mio codice potrebbe essere valutato con 10 byte in meno. Non lo so.
  • Grazie ai suggerimenti del golf C , in particolare nessun tipo di ritorno per operatore principale , EOF e operatore ternario , l'ultimo per la segnalazione che ?:può essere nidificato ,senza (…).

Puoi spostarti int c, m, f[3];all'esterno main, per evitare di dichiarare i tipi. Per le variabili globali, non è necessario dichiarare int. Ma in generale, soluzione interessante.
Konrad Borowski il

divertimento con fork () su Windows. heh

Questo non funziona per me. Comanda senza due volte l'output di una pipe e yes|head -3continua a funzionare per sempre e la shell esce dopo ogni singolo comando. Sto usando gcc versione 4.6.3 (Ubuntu / Linaro 4.6.3-1ubuntu5) senza alcun interruttore.
Dennis,

@Dennis: grazie per la segnalazione. Uso errato dell'operatore ternario. Avrei dovuto eseguire unit test prima di incollare, ma ero così sicuro ... Risolto ora, al costo di un altro byte.
MvG

Funziona bene ora. Penso che tu possa logorare altri 4 byte: 2 definendo la macro #define B break;case(il break;prima defaultdiventa )B-1:) e 2 sostituendo case'\n'e case'\'') con case 10e case 39.
Dennis,

3

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

Produrrà qualcosa del tipo:

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:~$

Questo invoca bash sul mio sistema, che non credo sia permesso
tecywiz121

Certo, ma dopo aver riletto la domanda, penso che questo non rompa alcuna regola (nessun sistema, nessun argomento, nessuna valutazione, fonte o punto ...)
F. Hauri,

Sì, ma in modo interessante: usare una sessione distaccata e invisibile per fare l'intero lavoro, che, prima di uscire, scarica l'intera cronologia sulla console iniziale.
F. Hauri,

Sto bene con questo abuso di regole. Secondo me è abbastanza intelligente - e la domanda consente un abuso delle regole. +1 da me.
Konrad Borowski il

1

Fattore (208 caratteri)

Poiché le regole non impediscono di scaricare il lavoro su terzi ( http://www.compileonline.com/execute_bash_online.php ), ecco una soluzione:

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 ;

Puoi scrivere il programma come una riga ancora più corta anche nel ricambio ( 201 caratteri):

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 ;

Immagino che non avrei dovuto permettere l'abuso delle regole. Oh giusto, l'ho fatto. +1 da me - non ci penserei mai.
Konrad Borowski,

0

Perl, 135 caratteri

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

Questa shell fa cose stupide. Avvia una shell interattiva con perl shell.ple provala:

  • lsstampa in una colonna, perché l'output standard non è un terminale. La shell reindirizza l'output standard su una pipe e legge dalla pipe.
  • perl -E 'say "hi"; sleep 1' attende 1 secondo per dire ciao, perché la shell ritarda l'output.
  • ddlegge 0 byte, a meno che non sia il primo comando a questa shell. La shell reindirizza l'input standard da una pipe vuota, per ogni pipeline dopo la prima.
  • perl -e '$0 = screamer; print "A" x 1000000' | dd of=/dev/null si completa con successo.
  • perl -e '$0 = screamer; print "A" x 1000000' | cat | dd of=/dev/null pende il guscio!
    • Bug n. 1: la shell attende stupidamente il primo comando prima di avviare il terzo comando nella stessa pipeline. Quando i tubi sono pieni, il guscio entra in deadlock. Qui, la shell non inizia dd fino a quando l'urlo non esce, ma l'urlatore attende il gatto e il gatto aspetta la shell. Se uccidi Screamer (forse con pkill -f screamerun'altra shell), la shell riprende.
  • perl -e 'fork and exit; $0 = sleeper; sleep' pende il guscio!
    • Bug n. 2: la shell attende l'ultimo comando in una pipeline per chiudere la pipe di output. Se il comando termina senza chiudere la pipe, la shell continua ad attendere. Se uccidi il dormiente, la shell riprende.
  • 'echo $((2+3))'esegue il comando in / bin / sh. Questo è il comportamento del exec e del sistema di Perl con un argomento, ma solo se l'argomento contiene caratteri speciali.

Versione Ungolfed

#!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.
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.