쉘 스크립트의 서브 세트 구현


12

이 사이트는 태그 에서 다양한 언어를 구현하는 데 많은 문제가있었습니다 . 그러나 실제로 그들 모두는 아무도 사용하지 않는 난해한 언어였습니다. 여기에서 대부분의 사용자가 이미 알고있는 실용적인 언어에 대한 통역을 할 시간입니다. 예, 제목을 읽는 데 문제가있는 경우를 대비하여 쉘 스크립트입니다. (예, GolfScript 및 Befunge와 같은 언어가 모든 것을이기는 것에 지루해하기 때문에 의도적 으로이 도전을했습니다. 따라서 더 실용적인 프로그래밍 언어가 더 큰 우승 기회를 갖는 곳에 도전하십시오)

그러나 쉘 스크립트는 비교적 큰 언어이므로 구현하도록 요청하지 않습니다. 대신, 셸 스크립트 기능의 작은 하위 집합을 만들려고합니다.

내가 결정한 하위 집합은 다음과 같은 하위 집합입니다.

  • 프로그램 실행 (단일 인용 부호가 허용 되더라도 프로그램에는 문자 만 포함됨)
  • 프로그램 인수
  • 작은 따옴표 (공백을 포함하여 인쇄 가능한 ASCII 문자 허용, 작은 따옴표 제외)
  • 따옴표없는 문자열 (ASCII 문자, 숫자 및 대시 허용)
  • 파이프
  • 빈 문장
  • 줄 바꿈으로 구분 된 여러 문장
  • 후행 / 리딩 / 다중 공간

이 태스크에서는 STDIN의 입력을 읽고 요청 된 모든 명령을 실행해야합니다. POSIX 호환 운영 체제를 안전하게 사용할 수 있으므로 Windows와 같은 이식성이 필요하지 않습니다. 다른 프로그램으로 파이프되지 않은 프로그램은 STDIN에서 읽지 않는다고 안전하게 가정 할 수 있습니다. 명령이 존재한다고 가정 할 수 있습니다. 다른 것은 사용되지 않을 것입니다. 안전한 가정이 깨지면 무엇이든 할 수 있습니다. 최대 15 개의 인수와 512 자 미만의 행을 안전하게 가정 할 수 있습니다 (명확한 메모리 할당 또는 무언가가 필요한 경우 C가 여전히 작더라도 C에서 이길 수있는 기회는 거의 없습니다). 파일 디스크립터를 정리할 필요가 없습니다.

전체 라인을받은 후 또는 STDIN이 끝난 후에도 언제든지 프로그램을 실행할 수 있습니다. 원하는 접근 방식을 선택하십시오.

쉘을 테스트 할 수있는 간단한 테스트 케이스 (세 번째 명령 후 후행 공백에 주목) :

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

위의 프로그램은 다음과 같은 결과를 출력해야합니다.

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

명령에 대한 인수가 없으면 셸 자체를 실행할 수 없습니다 (이 예외는 Perl에 대해 이루어졌습니다. Perl은 인수를 넣을 때 셸에서 명령을 실행 system하지만 다른 경우에는이 예외를 남용 할 수 있습니다) 문자를 저장하는 방식으로 그렇게 할 수 있다면) 또는 실행하는 명령은 쉘 자체입니다. 많은 언어가 system쉘을 실행하는 함수를 가지고 있기 때문에 이것은 아마도이 도전에서 가장 큰 문제 일 것입니다 . 대신 subprocess파이썬의 모듈 처럼 프로그램을 직접 호출하는 언어 API를 사용하십시오 . 이것은 어쨌든 보안에 대한 좋은 아이디어이며, 안전하지 않은 쉘을 만들고 싶지 않을까요? 이것은 아마도 PHP를 중지시킬 수 있지만 어쨌든 선택할 다른 언어가 있습니다.

당신이 쉘 스크립트에서 프로그램을 만들려고하는 경우에는 사용할 수 없습니다 eval, source또는 .(함수가 아닌 문자에서와 같이). 내 의견으로는 도전이 너무 쉬울 것입니다.

영리한 규칙 남용이 허용되었습니다. 내가 명시 적으로 허용하지 않은 것들이 많이 있지만, 나는 당신이 여전히 내가하지 않은 일들을 할 수 있다고 확신합니다. 때때로 사람들이 내 규칙을 해석하는 방식에 놀랐습니다. 또한 내가 언급하지 않은 것은 무엇이든 할 수 있다는 것을 기억하십시오. 예를 들어 변수를 사용하려고하면 하드 디스크를 지울 수 있지만하지 마십시오.

이것이 코드 골프이기 때문에 가장 짧은 코드가 승리합니다.


파이프 ... 왜 파이프 여야했는지 ...
JB

1
@ JB : 파이프 라인이없는 쉘 스크립트는 UNIX 쉘의 코드 흐름이 파이프를 기반으로하기 때문에 쉘 스크립트가 아닙니다.
Konrad Borowski

동의한다. 나는 여전히 구현해야 할 과제 중 가장 고통스러운 부분을 손에 넣었다고 생각합니다.
JB

@JB 동의합니다. 나는 이것을 건너 뜁니다.
Timtech

4
나는 도전을 모두 건너 뛰고 있음을 의미했습니다.
Timtech

답변:


7

배쉬 (92 바이트)

이 답변 과 동일한 허점을 활용 하면 훨씬 짧은 솔루션이 있습니다.

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

파이썬 ( 247 241 239 바이트)

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

멋지다. 수행 할 수있는 최적화가 *있지만 ( 전에 공백을 제거하는 것과 같이 ) 그 외에는 훌륭하게 보입니다 :-). 새로운 회원이 어려운 문제에 대해 좋은 해결책을 만들었다는 것에 놀랐습니다.
Konrad Borowski

@xfix 감사합니다! 나는이 도전을 정말로 즐겼다 ​​:-)
tecywiz121

10

C (340 바이트)

나는 골프에 전혀 경험이 없지만, 당신은 어딘가에서 시작해야합니다.

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

줄 바꿈을 추가 했으므로 스크롤 할 필요가 없지만 의미 상 의미가 없기 때문에 내 수에 포함시키지 않았습니다. 전 처리기 지시문 이후의 명령이 필요하고 계산되었습니다.

언 골프 버전

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

풍모

  • 병렬 실행 : 이전 명령이 계속 실행되는 동안 다음 명령을 입력 할 수 있습니다.
  • 파이프 계속 : 파이프 문자 다음에 줄 바꿈을 입력하고 다음 줄에서 명령을 계속할 수 있습니다.
  • 인접한 단어 / 문자열의 올바른 처리 : 'ec'ho He'll''o 'world작업과 같은 것 . 이 기능이 없으면 코드가 단순 해졌을 수도 있으므로, 이것이 필요한지 명확하게 설명하겠습니다.

알려진 문제

  • 파일 디스크립터의 절반은 닫히지 않으며 자식 프로세스는 절대로 거두지 않습니다. 장기적으로 이것은 일종의 리소스 소모를 유발할 수 있습니다.
  • 프로그램이 입력을 읽으려고하면 쉘이 동일한 소스에서 동시에 입력을 읽으므로 동작이 정의되지 않습니다.
  • execvp잘못 입력 한 프로그램 이름으로 인해 호출에 실패 하면 어떤 일이 발생할 수 있습니다 . 그런 다음 쉘이되는 동시에 두 가지 프로세스가 진행됩니다.
  • 특수 문자 '|' 줄 바꿈은 인용 된 문자열 내에서 특별한 의미를 유지합니다. 이것은 요구 사항을 위반 하므로이 문제를 해결하는 방법을 조사 중입니다. 약 11 바이트의 비용으로 수정되었습니다.

기타 사항

  • 그것은 분명히 단일 헤더를 포함하지 않으므로 사용 된 모든 함수의 암시 적 선언에 달려 있습니다. 호출 규칙에 따라 문제가 될 수도 있고 아닐 수도 있습니다.
  • 처음에는 echo 'foo bar baz' | sed 's/bar/BAR/' | sed 's/baz/zap/'멈추는 버그가있었습니다 . 문제는 분명히 닫히지 않은 쓰기 파이프 였기 때문에 닫기 명령을 추가해야했기 때문에 코드 크기가 10 바이트 증가했습니다. 이 상황이 발생하지 않는 시스템이있을 수 있으므로 내 코드의 등급이 10 바이트 미만일 수 있습니다. 모르겠어요
  • 덕분 는 C 골프 팁 , 특히 더 리턴 형 메인에 , EOF 처리삼항 연산자 , 그 지적에 대해 마지막 하나가 ?:중첩 된 수 ,없이 (…).

유형 선언을 피하기 위해 int c, m, f[3];외부 로 이동할 수 있습니다 main. 전역 변수의 경우 선언 할 필요가 없습니다 int. 그러나 일반적으로 흥미로운 해결책입니다.
Konrad Borowski

Windows에서 fork ()를 사용하면 재미 있습니다. heh

이것은 나를 위해 작동하지 않습니다. 파이프가없는 명령은 두 번 출력되고 yes|head -3계속 진행되며 쉘은 모든 단일 명령 후에 종료됩니다. 스위치없이 gcc 버전 4.6.3 (Ubuntu / Linaro 4.6.3-1ubuntu5)을 사용하고 있습니다.
데니스

@ 데니스 : 보고서 주셔서 감사합니다. 삼항 연산자를 잘못 사용했습니다. 붙여 넣기 전에 단위 테스트를 실행해야했지만 너무 확신했습니다. 이제 1 바이트를 더 지불했습니다.
MvG

지금은 잘 작동합니다. 매크로 정의하여 2 : 당신이 4 바이트 이상 떨어져 비벼서 따뜻하게 할 수 있다고 생각 #define B break;case합니다 (이 break;이전 default하게 )B-1:대체하여)과 2 case'\n'case'\''함께) case 10case 39.
Dennis

3

배쉬 (+ 화면) 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'

다음과 같이 출력됩니다 :

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

이것은 내 시스템에서 bash를 호출하는데, 허용되지 않는 것으로 생각된다
tecywiz121

물론, 그러나 질문을 다시 읽은 후에는 이것이 어떤 규칙도 위반하지 않는다고 생각합니다 (시스템, 논쟁, 평가, 출처 또는 점 ...)
F. Hauri

예, 그러나 interresting 방식으로 : 종료하기 전에 초기 콘솔에서 전체 히스토리를 덤프하는 것보다 분리 되고 보이지 않는 세션을 사용 하여 전체 작업을 수행하십시오.
F. Hauri

나는이 규칙 남용으로 괜찮습니다. 제 생각에는 영리합니다. 질문은 영리한 규칙 남용을 허용합니다. 나에게서 +1
Konrad Borowski

1

요소 (208 자)

이 규칙은 저작물을 제 3 자 ( http://www.compileonline.com/execute_bash_online.php ) 에게 오프로드하는 것을 허용하지 않으므로 다음과 같은 해결책이 있습니다.

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 ;

프로그램을 repl에서 더 짧은 한 줄짜리로 작성할 수도 있습니다 ( 201 자).

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 ;

나는 규칙 남용을 허용해서는 안된다고 생각합니다. 아 맞다. 나에게서 +1-나는 이것을 결코 생각하지 않을 것입니다.
Konrad Borowski

0

펄, 135 자

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

이 껍질은 바보 같은 짓을합니다. 대화식 쉘을 시작 perl shell.pl하고 시도하십시오.

  • ls표준 출력은 터미널이 아니기 때문에 한 열에 인쇄됩니다. 쉘은 표준 출력을 파이프로 리디렉션하고 파이프에서 읽습니다.
  • perl -E 'say "hi"; sleep 1' 쉘이 출력을 지연시키기 때문에 1 초 동안 hi를 기다립니다.
  • dd이 쉘의 첫 번째 명령이 아닌 경우 0 바이트를 읽습니다. 셸은 첫 번째 파이프 라인 이후의 모든 파이프 라인에 대해 빈 파이프의 표준 입력을 리디렉션합니다.
  • perl -e '$0 = screamer; print "A" x 1000000' | dd of=/dev/null 성공적으로 완료됩니다.
  • perl -e '$0 = screamer; print "A" x 1000000' | cat | dd of=/dev/null 껍질을 단다!
    • 버그 # 1 : 셸은 동일한 파이프 라인에서 세 번째 명령을 시작하기 전에 첫 번째 명령을 어리석게 기다립니다. 파이프가 가득 차면 쉘이 교착 상태로 들어갑니다. 여기서, screamer가 종료 될 때까지 쉘은 dd를 시작하지 않지만 screamer는 고양이를 기다리며 cat은 쉘을 기다립니다. 비명을 죽이면 (아마도 pkill -f screamer다른 껍질에서) 껍질이 다시 시작됩니다.
  • perl -e 'fork and exit; $0 = sleeper; sleep' 껍질을 단다!
    • 버그 # 2 : 셸은 파이프 라인의 마지막 명령이 출력 파이프를 닫을 때까지 기다립니다. 파이프를 닫지 않고 명령이 종료되면 쉘은 계속 대기합니다. 슬리퍼를 죽이면 껍질이 재개됩니다.
  • 'echo $((2+3))'/ bin / sh에서 명령을 실행합니다. 이것은 하나의 인수를 가진 Perl의 exec시스템 의 동작 이지만 인수에 특수 문자가 포함 된 경우에만 해당됩니다.

언 골프 버전

#!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.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.