ใช้ชุดย่อยของเชลล์สคริปต์


12

ไซต์นี้มีปัญหามากมายเกี่ยวกับการใช้ภาษาต่างๆในแท็กอย่างไรก็ตามจริง ๆ แล้วพวกเขาทั้งหมดเป็นภาษาลึกลับที่ไม่มีใครใช้ ใช้เวลาในการสร้างล่ามสำหรับภาษาที่ใช้งานได้จริงซึ่งผู้ใช้ส่วนใหญ่ที่นี่อาจรู้จักอยู่แล้ว ใช่มันเป็นเชลล์สคริปต์ในกรณีที่คุณมีปัญหาในการอ่านชื่อ (ไม่ใช่ว่าคุณมี) (ใช่ฉันตั้งใจทำสิ่งนี้อย่างท้าทายเพราะฉันเบื่อภาษาอย่าง GolfScript และ Befunge ชนะทุกอย่างดังนั้นฉันจึงวางความท้าทายที่ภาษาการเขียนโปรแกรมเชิงปฏิบัติมากขึ้นมีโอกาสชนะมากขึ้น)

อย่างไรก็ตามเชลล์สคริปต์เป็นภาษาที่ค่อนข้างใหญ่ดังนั้นฉันจะไม่ขอให้คุณใช้มัน ฉันจะสร้างชุดย่อยของฟังก์ชันเชลล์สคริปต์แทน

ชุดย่อยที่ฉันเลือกคือชุดย่อยต่อไปนี้:

  • การดำเนินการโปรแกรม (โปรแกรมจะมีตัวอักษรเท่านั้นแม้ว่าจะอนุญาตให้ใช้เครื่องหมายคำพูดเดี่ยว)
  • ข้อโต้แย้งของโปรแกรม
  • เครื่องหมายคำพูดเดี่ยว (ยอมรับอักขระ ASCII ที่พิมพ์ได้รวมถึงช่องว่างยกเว้นคำพูดเดียว)
  • สตริงที่ไม่ได้ระบุไว้ (อนุญาตให้ตัวอักษรตัวเลขและขีดกลาง ASCII)
  • ท่อ
  • งบเปล่า
  • หลายคำสั่งคั่นด้วยบรรทัดใหม่
  • ส่วนต่อท้าย / นำหน้า / หลายช่องว่าง

ในภารกิจนี้คุณต้องอ่านอินพุตจาก STDIN และรันทุกคำสั่งที่ร้องขอ คุณสามารถสมมติระบบปฏิบัติการที่รองรับ POSIX ได้อย่างปลอดภัยดังนั้นจึงไม่จำเป็นต้องพกพากับ Windows หรืออะไรทำนองนั้น คุณสามารถสรุปได้อย่างปลอดภัยว่าโปรแกรมที่ไม่ได้ถูกไพพ์ไปยังโปรแกรมอื่นจะไม่อ่านจาก STDIN คุณสามารถสันนิษฐานได้อย่างปลอดภัยว่าคำสั่งนั้นจะมีอยู่ คุณสามารถสรุปได้อย่างปลอดภัยว่าจะไม่มีการใช้อะไรอีก หากข้อสันนิษฐานที่ปลอดภัยถูกทำลายคุณสามารถทำอะไรก็ได้ คุณสามารถสรุปได้อย่างปลอดภัยที่สุด 15 ข้อโต้แย้งและบรรทัดที่ต่ำกว่า 512 ตัวอักษร (ถ้าคุณต้องการการจัดสรรหน่วยความจำอย่างชัดเจนหรือบางสิ่งบางอย่าง - ฉันจะให้โอกาสน้อยมากในการชนะ C แม้ว่ามันจะยังเล็กก็ตาม) คุณไม่ต้องล้างไฟล์ descriptors

คุณได้รับอนุญาตให้รันโปรแกรมได้ทุกเมื่อ - แม้หลังจากได้รับข้อมูลเต็มบรรทัดหรือหลังจาก 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 ซึ่งจะรันคำสั่งในเชลล์เมื่อใส่อาร์กิวเมนต์เพียงsystemแต่อย่าลังเลที่จะใช้ข้อยกเว้นนี้สำหรับผู้อื่น ภาษาด้วยหากคุณสามารถทำได้ในลักษณะที่ช่วยประหยัดอักขระ) หรือคำสั่งที่คุณใช้คือเชลล์เอง นี่อาจเป็นปัญหาที่ใหญ่ที่สุดในการท้าทายนี้เนื่องจากมีหลายภาษาที่มีsystemฟังก์ชั่นที่ใช้เชลล์ ใช้ API ภาษาที่เรียกใช้โปรแกรมโดยตรงเช่นsubprocessโมดูลใน Python นี่เป็นความคิดที่ดีสำหรับความปลอดภัยอย่างไรก็ตามคุณไม่ต้องการสร้างเชลล์ที่ไม่ปลอดภัยคุณต้องการทำอะไร? สิ่งนี้น่าจะหยุด PHP แต่มีภาษาอื่นให้เลือกอยู่ดี

หากคุณกำลังจะทำให้โปรแกรมของคุณในสคริปต์เปลือกคุณจะไม่ได้รับอนุญาตให้ใช้eval, sourceหรือ.(ในขณะที่ฟังก์ชั่นไม่ได้ตัวอักษร) มันจะทำให้การท้าทายง่ายเกินไปในความคิดของฉัน

อนุญาตการละเมิดกฎที่ฉลาด มีหลายสิ่งที่ฉันไม่อนุญาตอย่างชัดเจน แต่ฉันเกือบแน่ใจว่าคุณยังได้รับอนุญาตให้ทำสิ่งที่ฉันไม่ได้คิด บางครั้งฉันก็แปลกใจที่ผู้คนตีความกฎของฉัน นอกจากนี้จำไว้ว่าคุณสามารถทำอะไรก็ได้เพื่อสิ่งที่ฉันไม่ได้พูดถึง ตัวอย่างเช่นถ้าฉันพยายามใช้ตัวแปรคุณสามารถล้างฮาร์ดดิสก์ (แต่โปรดอย่า)

รหัสที่สั้นที่สุดชนะเช่นนี้เป็น codegolf


ท่อ ... ทำไมต้องเป็นท่อ ...
JB

1
@JB: เชลล์สคริปต์ที่ไม่มีไพพ์ไลน์ไม่ใช่เชลล์สคริปต์ในความคิดของฉันเนื่องจากโฟลว์โค้ดในเชลล์ UNIX ขึ้นอยู่กับไพพ์
Konrad Borowski

ฉันเห็นด้วย. ฉันยังคงคิดว่ามันเป็นส่วนที่เจ็บปวดที่สุดของความท้าทายที่จะนำไปใช้
JB

@JB ฉันเห็นด้วย ฉันข้ามอันนี้
Timtech

4
ฉันหมายความว่าฉันข้ามความท้าทายไปโดยสิ้นเชิง
Timtech

คำตอบ:


7

Bash (92 ไบต์)

การใช้ประโยชน์จากช่องโหว่เดียวกันกับคำตอบนี้นี่เป็นคำตอบที่สั้นกว่ามาก:

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

Python ( 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;}}

ฉันได้เพิ่มตัวแบ่งบรรทัดเพื่อที่คุณจะได้ไม่ต้องเลื่อน แต่ไม่ได้รวมไว้ในการนับของฉัน ผู้ที่ต้องการคำสั่ง preprocessor และถูกนับ

เวอร์ชันที่ไม่ดี

#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

สนุกกับ fork () บน windows heh

มันไม่ทำงานสำหรับฉัน คำสั่งที่ไม่มีเอาต์พุตไพพ์สองครั้งและyes|head -3ดำเนินต่อไปเรื่อย ๆ และเชลล์จะออกหลังจากทุกคำสั่งเดียว ฉันใช้ gcc เวอร์ชั่น 4.6.3 (Ubuntu / Linaro 4.6.3-1ubuntu5) โดยไม่มีสวิตช์ใด ๆ
Dennis

@Dennis: ขอบคุณสำหรับรายงาน การใช้ตัวดำเนินการที่ไม่ถูกต้อง ฉันควรใช้การทดสอบหน่วยก่อนที่จะวาง แต่ฉันแน่ใจว่า ... คงที่ตอนนี้ด้วยค่าใช้จ่ายของอีกหนึ่งไบต์
MvG

มันใช้งานได้ดีในขณะนี้ ฉันคิดว่าคุณสามารถปิดเร่าร้อน 4 ไบต์เพิ่มเติมได้ที่: 2 โดยกำหนดแมโคร#define B break;case(คนbreak;ก่อนที่จะdefaultกลายเป็น)B-1:) และ 2 โดยการเปลี่ยนcase'\n'และcase'\'') ด้วยและcase 10 case 39
Dennis

3

bash (+ หน้าจอ) 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:~$

สิ่งนี้ก่อให้เกิดการทุบตีในระบบของฉันซึ่งฉันไม่คิดว่าจะได้รับอนุญาต
tecywiz121

แน่นอน แต่หลังจากอ่านคำถามอีกครั้งฉันคิดว่านี่จะไม่ผิดกฎใด ๆ (ไม่มีระบบไม่มีการโต้แย้งไม่มีการ
คัดค้าน

ใช่ แต่ด้วยวิธีการแบบสอดแทรก: การใช้เซสชันที่แยกออกและล่องหนเพื่อทำงานทั้งหมดยกเว้นก่อนที่จะออกจากการถ่ายโอนข้อมูลประวัติทั้งหมดบนคอนโซลเริ่มต้น
F. Hauri

ฉันสบายดีกับการละเมิดกฎนี้ มันฉลาดพอในความคิดของฉัน - และคำถามนี้อนุญาตการละเมิดกฎอย่างชาญฉลาด +1 จากฉัน
Konrad Borowski

1

ตัวประกอบ (208 ตัวอักษร)

เนื่องจากกฎไม่ได้ไม่อนุญาตการถ่ายโอนงานให้กับบุคคลที่สาม ( 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 ;

คุณสามารถเขียนโปรแกรมเป็นหนึ่งซับที่สั้นกว่าแม้ใน replicate ( 201 chars):

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

Perl, 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 วินาทีเพื่อกล่าวคำทักทายเพราะเชลล์เลื่อนการส่งออก
  • 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:เชลล์อย่างโง่ ๆ รอคำสั่งแรกก่อนที่จะเริ่มคำสั่งที่สามในไปป์ไลน์เดียวกัน เมื่อท่อเต็มเปลือกจะเข้าสู่การหยุดชะงัก ที่นี่เชลล์ไม่เริ่ม dd จนกว่าจะออกจาก screamer แต่ screamer รอ cat และ cat รอให้เชลล์ หากคุณฆ่า screamer (อาจมีpkill -f screamerในกระสุนอีกอัน) จากนั้นกระสุนจะกลับมาทำงานต่อ
  • perl -e 'fork and exit; $0 = sleeper; sleep' แฮงค์เปลือก!
    • ข้อผิดพลาด # 2:เชลล์รอคำสั่งสุดท้ายในไปป์ไลน์เพื่อปิดไพพ์เอาต์พุต หากคำสั่งออกโดยไม่ปิดไพพ์เชลล์จะยังคงรอต่อไป หากคุณฆ่าสลีปเปอร์จากนั้นเชลล์จะทำงานต่อ
  • 'echo $((2+3))'รันคำสั่งใน / bin / sh นี่คือพฤติกรรมของผู้บริหารและระบบของ Perl ด้วยอาร์กิวเมนต์เดียว แต่ถ้าอาร์กิวเมนต์นั้นมีอักขระพิเศษ

เวอร์ชันที่ไม่ดี

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