Terapkan subset skrip shell

12

Situs ini memiliki banyak masalah yang melibatkan penerapan berbagai bahasa dalam tag . Namun, praktis semua dari mereka adalah bahasa esoterik yang tidak ada yang menggunakan. Saatnya membuat penerjemah untuk bahasa praktis yang mungkin sudah diketahui sebagian besar pengguna di sini. Ya, ini skrip shell, jika Anda memiliki masalah dalam membaca judul (bukan yang Anda miliki). (ya, saya sengaja membuat tantangan ini, karena saya bosan dengan bahasa seperti GolfScript dan Befunge memenangkan segalanya, jadi saya menaruh beberapa tantangan di mana bahasa pemrograman yang lebih praktis memiliki peluang lebih besar untuk menang)

Namun, skrip shell adalah bahasa yang relatif besar, jadi saya tidak akan meminta Anda untuk mengimplementasikannya. Sebagai gantinya, saya akan membuat sebagian kecil dari fungsionalitas skrip shell.

Subset yang saya putuskan adalah subset berikut:

  • Akan tetapi, program yang dijalankan (program hanya akan berisi huruf, bahkan jika tanda kutip tunggal diizinkan)
  • Argumen program
  • Kutipan tunggal (menerima karakter ASCII yang dapat dicetak, termasuk spasi putih, tidak termasuk kutipan tunggal)
  • String yang tidak dikutip (memungkinkan huruf, angka, dan tanda hubung ASCII)
  • Pipa
  • Pernyataan kosong
  • Beberapa pernyataan dipisahkan oleh baris baru
  • Mengejar / memimpin / banyak ruang

Dalam tugas ini, Anda harus membaca input dari STDIN, dan menjalankan setiap perintah yang diminta. Anda dapat dengan aman menggunakan sistem operasi yang kompatibel dengan POSIX, jadi tidak perlu portabilitas dengan Windows, atau yang seperti itu. Anda dapat dengan aman berasumsi bahwa program yang tidak disalurkan ke program lain tidak akan membaca dari STDIN. Anda dapat dengan aman berasumsi bahwa perintah akan ada. Anda dapat dengan aman berasumsi bahwa tidak ada lagi yang akan digunakan. Jika beberapa asumsi yang aman rusak, Anda dapat melakukan apa saja. Anda dapat dengan aman mengasumsikan paling banyak 15 argumen, dan baris di bawah 512 karakter (jika Anda membutuhkan alokasi memori eksplisit, atau sesuatu - Saya benar-benar akan memberikan peluang kecil untuk menang untuk C, bahkan jika mereka masih kecil). Anda tidak perlu membersihkan deskriptor file.

Anda diizinkan untuk menjalankan program kapan saja - bahkan setelah menerima baris penuh, atau setelah STDIN berakhir. Pilih pendekatan yang Anda inginkan.

Testcase sederhana yang memungkinkan Anda menguji shell Anda (perhatikan spasi kosong setelah perintah ketiga):

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

Program di atas akan menampilkan hasil sebagai berikut:

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

Anda tidak diizinkan untuk mengeksekusi shell itu sendiri, kecuali jika Anda tidak memiliki argumen untuk perintah (pengecualian ini dibuat untuk Perl, yang menjalankan perintah di shell ketika mengajukan argumen saja system, tetapi jangan ragu untuk menggunakan pengecualian ini untuk yang lain bahasa juga, jika Anda bisa melakukannya dengan cara yang menghemat karakter), atau perintah yang Anda jalankan adalah shell itu sendiri. Ini mungkin masalah terbesar dalam tantangan ini, karena banyak bahasa memiliki systemfungsi yang mengeksekusi shell. Alih-alih menggunakan API bahasa yang memanggil program secara langsung, seperti subprocessmodul dengan Python. Bagaimanapun, ini adalah ide yang bagus untuk keamanan, dan Anda tidak ingin membuat shell yang tidak aman, bukan? Ini kemungkinan besar menghentikan PHP, tetapi ada bahasa lain yang harus dipilih.

Jika Anda akan membuat program anda dalam shell script, Anda tidak diizinkan untuk menggunakan eval, sourceatau .(seperti dalam, fungsi, bukan karakter). Itu akan membuat tantangannya terlalu mudah menurut saya.

Pelanggaran aturan pintar diizinkan. Ada banyak hal yang secara eksplisit saya tolak, tetapi saya hampir yakin bahwa Anda masih diperbolehkan melakukan hal-hal yang belum saya lakukan. Terkadang saya terkejut tentang bagaimana orang menafsirkan aturan saya. Juga, ingatlah bahwa Anda dapat melakukan apa saja untuk apa pun yang belum saya sebutkan. Misalnya, jika saya mencoba menggunakan variabel, Anda dapat menghapus hard disk (tapi tolong jangan).

Kode terpendek menang, karena ini adalah codegolf.

Konrad Borowski
sumber
Pipa ... Kenapa harus pipa ...
JB
1
@ JK: Script shell tanpa pipa bukan skrip shell menurut saya, karena aliran kode di shell UNIX didasarkan pada pipa.
Konrad Borowski
Saya setuju. Saya masih berpikir itu adalah bagian paling sulit dari tantangan untuk diterapkan.
JB
@ JP Saya setuju; Saya melewatkan yang ini.
Timtech
4
Maksud saya, saya melewatkan tantangan sama sekali.
Timtech

Jawaban:

7

Bash (92 byte)

Memanfaatkan celah yang sama dengan jawaban ini , berikut adalah solusi yang jauh lebih pendek:

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

Python ( 247 241 239 bytes)

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
sumber
Ini terlihat hebat. Ada beberapa optimasi yang dapat dilakukan (seperti menghapus spasi putih sebelumnya *), tetapi selain itu, ini terlihat hebat :-). Saya terkejut bahwa anggota baru membuat solusi yang bagus untuk masalah yang sulit.
Konrad Borowski
@ memperbaiki Terima kasih banyak! Saya sangat menikmati tantangan ini :-)
tecywiz121
10

C (340 byte)

Saya tidak punya pengalaman sama sekali dalam bermain golf, tetapi Anda harus mulai dari suatu tempat, jadi begini:

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

Saya menambahkan jeda baris sehingga Anda tidak perlu menggulir, tetapi tidak memasukkannya dalam hitungan saya karena tidak memiliki arti semantik. Mereka setelah arahan preprosesor diperlukan dan dihitung.

Versi tidak disatukan

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

fitur

  • Eksekusi paralel: Anda dapat mengetik perintah berikutnya ketika yang sebelumnya masih dieksekusi.
  • Kelanjutan pipa: Anda dapat memasukkan baris baru setelah karakter pipa dan melanjutkan perintah di baris berikutnya.
  • Penanganan kata / string yang benar: Hal-hal seperti 'ec'ho He'll''o 'worldpekerjaan sebagaimana mestinya. Mungkin saja kodenya akan lebih sederhana tanpa fitur ini, jadi saya akan menerima klarifikasi apakah ini diperlukan.

Masalah yang diketahui

  • Setengah deskriptor file tidak pernah ditutup, proses anak tidak pernah menuai. Dalam jangka panjang, ini kemungkinan akan menyebabkan semacam kehabisan sumber daya.
  • Jika suatu program mencoba membaca input, perilaku tidak ditentukan, karena shell saya membaca input dari sumber yang sama secara bersamaan.
  • Apa pun dapat terjadi jika execvppanggilan gagal, misalnya karena nama program salah ketik. Lalu kami memiliki dua proses bermain menjadi shell secara bersamaan.
  • Karakter khusus '|' dan line break mempertahankan makna khusus mereka di dalam string yang dikutip. Ini melanggar persyaratan, jadi saya sedang menyelidiki cara untuk memperbaikinya. Diperbaiki, dengan biaya sekitar 11 byte.

Catatan lain

  • Masalahnya jelas tidak termasuk satu header, jadi itu tergantung pada deklarasi implisit dari semua fungsi yang digunakan. Tergantung pada konvensi panggilan, ini mungkin atau mungkin tidak menjadi masalah.
  • Awalnya saya punya bug di mana echo 'foo bar baz' | sed 's/bar/BAR/' | sed 's/baz/zap/'hang. Masalahnya tampaknya adalah pipa tulis yang tidak tertutup, jadi saya harus menambahkan perintah tutup, yang meningkatkan ukuran kode saya sebesar 10 byte. Mungkin ada sistem di mana situasi ini tidak muncul, jadi kode saya mungkin dinilai dengan 10 byte lebih sedikit. Saya tidak tahu
  • Berkat tip golf C , khususnya tidak ada tipe pengembalian untuk operator utama , penanganan EOF dan ternary , yang terakhir untuk menunjukkan yang ?:dapat disarangkan ,tanpa (…).
MvG
sumber
Anda dapat pindah ke int c, m, f[3];luar main, untuk menghindari menyatakan jenis. Untuk variabel global, Anda tidak perlu mendeklarasikan int. Namun umumnya, solusi menarik.
Konrad Borowski
bersenang-senang dengan fork () di windows. heh
Ini tidak bekerja untuk saya. Perintah tanpa output pipa dua kali dan yes|head -3terus berjalan selamanya dan shell keluar setelah setiap perintah. Saya menggunakan versi gcc 4.6.3 (Ubuntu / Linaro 4.6.3-1ubuntu5) tanpa switch.
Dennis
@ Dennis: Terima kasih atas laporannya. Penggunaan yang salah dari operator ternary. Saya seharusnya menjalankan tes unit sebelum menempel, tapi saya sangat yakin ... Tetap sekarang, dengan biaya satu byte lagi.
MvG
Sekarang berfungsi dengan baik. Saya pikir Anda dapat melepaskan 4 byte lebih: 2 dengan mendefinisikan makro #define B break;case( break;sebelum defaultmenjadi )B-1:) dan 2 dengan mengganti case'\n'dan case'\'') dengan case 10dan case 39.
Dennis
3

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

Akan menampilkan sesuatu seperti:

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
sumber
Ini memanggil bash di sistem saya, yang saya pikir tidak diizinkan
tecywiz121
Tentu saja, tetapi setelah membaca kembali pertanyaan, saya pikir ini tidak melanggar aturan apa pun (Tidak ada sistem, tidak ada argumen, tidak ada sumber, sumber atau titik ...)
F. Hauri
Ya, tetapi dengan cara interresting: Menggunakan sesi terpisah dan tidak terlihat untuk melakukan seluruh pekerjaan, daripada, sebelum keluar, buang seluruh sejarah pada konsol awal.
F. Hauri
Saya setuju dengan penyalahgunaan aturan ini. Ini cukup pintar menurut saya - dan pertanyaannya memungkinkan penyalahgunaan aturan cerdas. +1 dari saya.
Konrad Borowski
1

Faktor (208 karakter)

Karena aturan tidak melarang pembongkaran pekerjaan ke pihak ketiga ( http://www.compileonline.com/execute_bash_online.php ), berikut ini solusinya:

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 ;

Anda dapat menulis program ini sebagai one-liner yang lebih pendek di repl juga ( 201 karakter):

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
sumber
Saya kira saya seharusnya tidak membiarkan penyalahgunaan aturan. Oh benar, benar. +1 dari saya - Saya tidak akan pernah memikirkan hal ini.
Konrad Borowski
0

Perl, 135 karakter

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

Shell ini melakukan beberapa hal bodoh. Mulai shell interaktif dengan perl shell.pldan coba:

  • lsdicetak dalam satu kolom, karena output standar bukan terminal. Shell mengalihkan output standar ke pipa dan membaca dari pipa.
  • perl -E 'say "hi"; sleep 1' menunggu 1 detik untuk menyapa, karena shell menunda output.
  • ddmembaca 0 byte, kecuali itu adalah perintah pertama ke shell ini. Shell mengalihkan input standar dari pipa kosong, untuk setiap pipa setelah yang pertama.
  • perl -e '$0 = screamer; print "A" x 1000000' | dd of=/dev/null berhasil diselesaikan.
  • perl -e '$0 = screamer; print "A" x 1000000' | cat | dd of=/dev/null hang shell!
    • Bug # 1: Shell bodoh menunggu perintah pertama sebelum memulai perintah ketiga dalam pipa yang sama. Ketika pipa-pipa penuh, shell memasuki jalan buntu. Di sini, shell tidak memulai dd sampai screamer keluar, tetapi screamer menunggu kucing, dan kucing menunggu shell. Jika Anda membunuh screamer (mungkin dengan pkill -f screamerdi shell lain), maka shell melanjutkan.
  • perl -e 'fork and exit; $0 = sleeper; sleep' hang shell!
    • Bug # 2: Shell menunggu perintah terakhir dalam pipa untuk menutup pipa keluaran. Jika perintah keluar tanpa menutup pipa, maka shell terus menunggu. Jika Anda membunuh sleeper, maka shell melanjutkan.
  • 'echo $((2+3))'menjalankan perintah di / bin / sh. Ini adalah perilaku eksekutif dan sistem Perl dengan satu argumen, tetapi hanya jika argumen tersebut berisi karakter khusus.

Versi tidak disatukan

#!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
sumber