Thực hiện tập hợp con của tập lệnh shell


12

Trang web này có rất nhiều vấn đề liên quan đến việc triển khai các ngôn ngữ khác nhau trong thẻ . Tuy nhiên, thực tế tất cả chúng là những ngôn ngữ bí truyền mà không ai sử dụng. Đã đến lúc làm phiên dịch cho một ngôn ngữ thực tế mà hầu hết người dùng ở đây có lẽ đã biết. Vâng, đó là kịch bản shell, trong trường hợp bạn gặp vấn đề khi đọc tiêu đề (không phải là bạn có). .

Tuy nhiên, tập lệnh shell là một ngôn ngữ tương đối lớn, vì vậy tôi sẽ không yêu cầu bạn thực hiện nó. Thay vào đó, tôi sẽ tạo một tập hợp nhỏ chức năng shell script.

Tập hợp con tôi quyết định là tập hợp con sau:

  • Thực hiện các chương trình (các chương trình sẽ chỉ chứa các chữ cái, tuy nhiên, ngay cả khi dấu ngoặc đơn được cho phép)
  • Đối số chương trình
  • Dấu ngoặc đơn (chấp nhận bất kỳ ký tự ASCII có thể in nào, bao gồm cả khoảng trắng, không bao gồm dấu ngoặc đơn)
  • Chuỗi không được trích dẫn (cho phép các chữ cái, số và dấu gạch ngang ASCII)
  • Ống
  • Báo cáo trống
  • Nhiều câu lệnh được phân tách bằng dòng mới
  • Trailing / hàng đầu / nhiều không gian

Trong tác vụ này, bạn phải đọc đầu vào từ STDIN và chạy mọi lệnh được yêu cầu. Bạn có thể giả định một cách an toàn hệ điều hành tương thích POSIX, do đó không cần tính di động với Windows, hoặc bất cứ điều gì tương tự. Bạn có thể giả định một cách an toàn rằng các chương trình không được chuyển sang các chương trình khác sẽ không được đọc từ STDIN. Bạn có thể an toàn cho rằng các lệnh sẽ tồn tại. Bạn có thể cho rằng không có gì khác sẽ được sử dụng. Nếu một số giả định an toàn bị phá vỡ, bạn có thể làm bất cứ điều gì. Bạn có thể giả định một cách an toàn tối đa 15 đối số và các dòng dưới 512 ký tự (nếu bạn cần phân bổ bộ nhớ rõ ràng hoặc một cái gì đó - tôi thực sự sẽ mang lại cơ hội chiến thắng nhỏ cho C, ngay cả khi chúng vẫn còn nhỏ). Bạn không phải dọn dẹp các mô tả tập tin.

Bạn được phép thực hiện các chương trình tại bất kỳ thời điểm nào - ngay cả sau khi nhận được dòng đầy đủ hoặc sau khi STDIN kết thúc. Chọn bất kỳ phương pháp nào bạn muốn.

Testcase đơn giản cho phép bạn kiểm tra shell của mình (lưu ý khoảng trắng tra cứu sau lệnh thứ ba):

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

Chương trình trên sẽ cho kết quả như sau:

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

Bạn không được phép tự thực thi shell, trừ khi bạn không có bất kỳ đối số nào cho lệnh (ngoại lệ này được tạo cho Perl, chạy lệnh trong shell khi đặt đối số vào system, nhưng hãy thoải mái lạm dụng ngoại lệ này cho ngoại lệ khác ngôn ngữ cũng vậy, nếu bạn có thể làm điều đó theo cách lưu các ký tự) hoặc lệnh bạn chạy là chính nó. Đây có lẽ là vấn đề lớn nhất trong thử thách này, vì nhiều ngôn ngữ có systemchức năng thực thi shell. Thay vào đó, hãy sử dụng API ngôn ngữ gọi các chương trình trực tiếp, như subprocessmô-đun trong Python. Dù sao đây cũng là một ý tưởng tốt cho bảo mật, và tốt, bạn sẽ không muốn tạo một lớp vỏ không an toàn, bạn có muốn không? Điều này rất có thể dừng PHP, nhưng vẫn có những ngôn ngữ khác để chọn.

Nếu bạn đang đi để làm cho chương trình của bạn trong shell script, bạn không được phép sử dụng eval, sourcehoặc .(như trong, một chức năng, không phải là một nhân vật). Nó sẽ làm cho thách thức quá dễ dàng theo ý kiến ​​của tôi.

Lạm dụng quy tắc thông minh cho phép. Có rất nhiều điều tôi không được phép rõ ràng, nhưng tôi gần như chắc chắn rằng bạn vẫn được phép làm những việc mà tôi chưa từng làm. Đôi khi tôi ngạc nhiên về cách mọi người giải thích các quy tắc của tôi. Ngoài ra, hãy nhớ rằng bạn có thể làm bất cứ điều gì cho bất cứ điều gì tôi chưa đề cập. Ví dụ: nếu tôi cố gắng sử dụng các biến, bạn có thể xóa đĩa cứng (nhưng vui lòng không).

Mã ngắn nhất sẽ thắng, vì đây là codegolf.


Đường ống ... Tại sao nó phải là đường ống ...
JB

1
@JB: Theo tôi, kịch bản shell không có đường ống không phải là kịch bản shell, vì dòng mã trong shell UNIX dựa trên các đường ống.
Konrad Borowski

Tôi đồng ý. Tôi vẫn nghĩ rằng đó là phần khó khăn nhất của thử thách phải thực hiện.
JB

@JB Tôi đồng ý; Tôi đang bỏ qua cái này
TimTech

4
Ý tôi là tôi bỏ qua thử thách hoàn toàn.
TimTech

Câu trả lời:


7

Bash (92 byte)

Tận dụng lỗ hổng tương tự như câu trả lời này , đây là một giải pháp ngắn hơn nhiều:

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

Điều này có vẻ tuyệt vời. Có một số tối ưu hóa có thể được thực hiện (như xóa khoảng trắng trước đó *), nhưng ngoài ra, nó trông rất tuyệt :-). Tôi ngạc nhiên khi một thành viên mới thực hiện một giải pháp tốt cho một vấn đề khó khăn.
Konrad Borowski

@xfix Cảm ơn rất nhiều! Tôi thực sự rất thích thử thách này :-)
tecywiz121

10

C (340 byte)

Tôi không có kinh nghiệm gì về việc chơi golf, nhưng bạn phải bắt đầu ở đâu đó, vì vậy hãy đến đây:

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

Tôi đã thêm ngắt dòng để bạn sẽ không phải cuộn, nhưng không bao gồm chúng trong số của tôi vì chúng không có ý nghĩa ngữ nghĩa. Những người sau chỉ thị tiền xử lý được yêu cầu và được tính.

Phiên bản ung dung

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

Đặc trưng

  • Thực thi song song: bạn có thể gõ lệnh tiếp theo trong khi lệnh trước vẫn đang thực thi.
  • Tiếp tục các đường ống: bạn có thể nhập một dòng mới sau một ký tự ống và tiếp tục lệnh trên dòng tiếp theo.
  • Xử lý chính xác các từ / chuỗi liền kề: Những thứ như 'ec'ho He'll''o 'worldcông việc cần thiết. Cũng có thể là mã sẽ đơn giản hơn nếu không có tính năng này, vì vậy tôi sẽ hoan nghênh làm rõ liệu điều này có bắt buộc không.

Vấn đề đã biết

  • Một nửa các mô tả tập tin không bao giờ được đóng lại, các tiến trình con không bao giờ gặt hái được. Về lâu dài, điều này có thể sẽ gây ra một số loại cạn kiệt tài nguyên.
  • Nếu một chương trình cố gắng đọc đầu vào, hành vi không được xác định, vì shell của tôi đọc đầu vào từ cùng một nguồn cùng một lúc.
  • Bất cứ điều gì có thể xảy ra nếu execvpcuộc gọi thất bại, ví dụ do tên chương trình bị nhập sai. Sau đó, chúng tôi có hai quá trình chơi tại vỏ đồng thời.
  • Ký tự đặc biệt '|' và ngắt dòng giữ lại ý nghĩa đặc biệt của chúng bên trong chuỗi trích dẫn. Điều này vi phạm các yêu cầu, vì vậy tôi đang nghiên cứu các cách khắc phục vấn đề này. Đã sửa, với chi phí khoảng 11 byte.

Ghi chú khác

  • Điều rõ ràng là không bao gồm một tiêu đề duy nhất, vì vậy nó phụ thuộc vào khai báo ngầm của tất cả các chức năng được sử dụng. Tùy thuộc vào các quy ước gọi, điều này có thể hoặc không thể là một vấn đề.
  • Ban đầu tôi có một lỗi echo 'foo bar baz' | sed 's/bar/BAR/' | sed 's/baz/zap/' treo. Vấn đề rõ ràng là đường ống ghi không được tiết lộ, vì vậy tôi đã phải thêm lệnh đóng đó, làm tăng kích thước mã của tôi thêm 10 byte. Có lẽ có những hệ thống mà tình huống này không phát sinh, vì vậy mã của tôi có thể được đánh giá với 10 byte ít hơn. Tôi không biết.
  • Nhờ các mẹo chơi gôn C , đặc biệt không có loại trả về cho chính , xử lý EOFtoán tử ternary , loại cuối cùng để chỉ ra rằng ?:có thể được lồng ,mà không có (…).

Bạn có thể di chuyển ra int c, m, f[3];ngoài main, để tránh khai báo các loại. Đối với các biến toàn cục, bạn không phải khai báo int. Nhưng nói chung, giải pháp thú vị.
Konrad Borowski

vui vẻ với ngã ba () trên cửa sổ. heh

Điều này không làm việc cho tôi. Các lệnh không có đầu ra ống hai lần và yes|head -3tiếp tục chạy mãi và shell thoát ra sau mỗi lệnh. Tôi đang sử dụng gcc phiên bản 4.6.3 (Ubuntu / Linaro 4.6.3-1ubfox5) mà không có bất kỳ công tắc nào.
Dennis

@Dennis: Cảm ơn báo cáo. Sử dụng không chính xác của toán tử ternary. Tôi nên chạy các bài kiểm tra đơn vị trước khi dán, nhưng tôi rất chắc chắn rằng giờ đây đã cố định, với chi phí thêm một byte.
MvG

Nó hoạt động tốt bây giờ. Tôi nghĩ bạn có thể loại bỏ thêm 4 byte: 2 bằng cách xác định macro #define B break;case( break;trước defaulttrở thành )B-1:) và 2 bằng cách thay thế case'\n'case'\'') bằng case 10case 39.
Dennis

3

bash (+ màn hình) 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'

Sẽ xuất ra một cái gì đó như:

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

Điều này gọi bash trên hệ thống của tôi, điều mà tôi không nghĩ là được cho phép
tecywiz121

Tất nhiên, nhưng sau khi đọc lại câu hỏi, tôi nghĩ rằng điều này không vi phạm bất kỳ quy tắc nào (Không có hệ thống, không tranh luận, không có eval, nguồn hoặc dấu chấm ...)
F. Hauri

Có, nhưng theo một cách xen kẽ: Sử dụng phiên tách rờivô hình để thực hiện toàn bộ công việc, hơn là, trước khi thoát, đổ toàn bộ lịch sử vào bảng điều khiển ban đầu.
F. Hauri

Tôi ổn với việc lạm dụng quy tắc này. Theo tôi thì nó đủ thông minh - và câu hỏi cho phép lạm dụng quy tắc thông minh. +1 từ tôi.
Konrad Borowski

1

Yếu tố (208 ký tự)

Vì các quy tắc không cho phép giảm tải công việc cho bên thứ ba ( http://www.compileonline.com/execute_bash_online.php ), đây là một giải pháp:

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 ;

Bạn có thể viết chương trình dưới dạng một lớp lót thậm chí còn ngắn hơn trong phần thay thế ( 201 ký tự):

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 ;

Tôi đoán tôi không nên cho phép lạm dụng quy tắc. Ồ đúng rồi, tôi đã làm. +1 từ tôi - tôi sẽ không bao giờ nghĩ về điều này.
Konrad Borowski

0

Perl, 135 ký tự

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

Vỏ này làm một số điều ngu ngốc. Bắt đầu một vỏ tương tác với perl shell.plvà thử nó:

  • lsin trong một cột, bởi vì đầu ra tiêu chuẩn không phải là một thiết bị đầu cuối. Vỏ chuyển hướng đầu ra tiêu chuẩn đến một đường ống và đọc từ đường ống.
  • perl -E 'say "hi"; sleep 1' Đợi 1 giây để nói hi, vì shell bị trễ đầu ra.
  • ddđọc 0 byte, trừ khi đó là lệnh đầu tiên cho shell này. Vỏ chuyển hướng đầu vào tiêu chuẩn từ một ống rỗng, cho mọi đường ống sau ống đầu tiên.
  • perl -e '$0 = screamer; print "A" x 1000000' | dd of=/dev/null hoàn thành thành công
  • perl -e '$0 = screamer; print "A" x 1000000' | cat | dd of=/dev/null treo vỏ!
    • Lỗi # 1: Shell ngu ngốc chờ lệnh đầu tiên trước khi bắt đầu lệnh thứ ba trong cùng một đường ống. Khi các đường ống đầy, vỏ rơi vào bế tắc. Ở đây, cái vỏ không bắt đầu dd cho đến khi tiếng hét thoát ra, nhưng tiếng hét chờ con mèo và con mèo đợi cái vỏ. Nếu bạn giết người hét (có lẽ với pkill -f screamervỏ khác), thì vỏ lại tiếp tục.
  • perl -e 'fork and exit; $0 = sleeper; sleep' treo vỏ!
    • Lỗi # 2: Shell chờ lệnh cuối cùng trong đường ống để đóng ống đầu ra. Nếu lệnh thoát mà không đóng đường ống, thì vỏ tiếp tục chờ. Nếu bạn giết người ngủ, thì vỏ lại tiếp tục.
  • 'echo $((2+3))'chạy lệnh trong / bin / sh. Đây là hành vi của exechệ thống của Perl với một đối số, nhưng chỉ khi đối số chứa các ký tự đặc biệt.

Phiên bản ung dung

#!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.
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.