Реалізація підмножини скрипту оболонки


12

На цьому веб-сайті було багато проблем, пов’язаних із впровадженням різних мов у тег . Однак практично всі вони були езотеричними мовами, якими ніхто не користується. Час зробити перекладача на практичній мові, який, мабуть, знають більшість користувачів тут. Так, це сценарій оболонки, якщо у вас виникли проблеми з читанням назви (не те, що у вас є). (так, я навмисно зробив це завдання, оскільки мені нудно таких мов, як GolfScript і Befunge, що все перемагають, тому я ставлю певну проблему, коли більш практична мова програмування має більші шанси на перемогу)

Однак скрипт оболонки є досить великою мовою, тому я не прошу вас реалізувати його. Натомість я зроблю невелику підмножину функцій скрипту оболонки.

Підмножина, на яку я визначився, є наступною підмножиною:

  • Виконання програм (програми будуть містити лише літери, однак, навіть якщо дозволені окремі лапки)
  • Програмні аргументи
  • Одиночні лапки (приймаючи будь-який символ, що можна надрукувати ASCII, включаючи пробіли, за винятком однієї лапки)
  • Рядки без котирування (дозволяючи ASCII букви, цифри та тире)
  • Труби
  • Порожні заяви
  • Кілька висловлювань, розділених новим рядком
  • Трейлінг / ведучий / кілька пробілів

У цьому завданні ви повинні прочитати вхід зі STDIN та виконати кожну запитувану команду. Ви можете сміливо припускати сумісну з POSIX операційну систему, тому немає потреби в портативності з Windows, або нічого подібного. Ви можете сміливо припускати, що програми, які не передаються в інші програми, не будуть читатись із STDIN. Можна сміливо припускати, що команди існуватимуть. Можна сміливо припускати, що більше нічого не буде використано. Якщо якесь безпечне припущення порушено, ви можете зробити все, що завгодно. Ви можете сміливо припускати щонайбільше 15 аргументів і рядки нижче 512 символів (якщо вам потрібно чітке виділення пам'яті, або щось таке - я дійсно збираюся дати невеликі шанси виграти 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, який виконує команду в оболонці при введенні просто аргументу system, але сміливо зловживайте цим винятком для інших мови, якщо ви можете це зробити таким чином, щоб зберегти символи), або команда, яку ви запускаєте, - сама оболонка. Це, мабуть, найбільша проблема в цьому виклику, оскільки багато мов мають systemфункції, які виконують оболонку. Замість цього використовуйте API мови, які викликають програми безпосередньо, як-от subprocessмодуль в Python. Це все-таки хороша ідея для безпеки, і ну, ви б не хотіли створювати незахищену оболонку, хочете? Це, швидше за все, зупиняє PHP, але все-таки можна вибрати інші мови.

Якщо ви збираєтеся зробити вашу програму сценарію оболонки, ви не можете використовувати eval, sourceабо .(як у функції, а не символ). На мій погляд, це зробить виклик занадто простим.

Дозволено зловмисне використання правил. Є багато речей, які я прямо забороняю, але я майже впевнений, що вам все одно дозволяють робити те, чого я ще не хотів. Іноді я дивуюсь тому, як люди трактують мої правила. Також пам’ятайте, що ви можете робити все, що б я не говорив. Наприклад, якщо я спробую використовувати змінні, ви можете стерти жорсткий диск (але, будь ласка, не варто).

Виграє найкоротший код, оскільки це кодовий гольф.


Труби ... Навіщо це мали бути труби ...
JB

1
@JB: Сценарій оболонки без трубопроводів, на мою думку, не є оболонковим сценарієм, оскільки потік коду в оболонці UNIX базується на трубах.
Конрад Боровський

Я згоден. Я все ще вважаю, що це руками найболючіша частина завдання, яку потрібно здійснити.
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(),

Це виглядає чудово. Є деякі оптимізації, які можна зробити (наприклад, видалити пробіли раніше *), але крім цього, це чудово виглядає :-). Я здивований, що новий член склав таке вдале рішення важкої проблеми.
Конрад Боровський

@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/'зависла. Очевидно, що проблема полягала в незамкненому каналі запису, тому мені довелося додати ту команду close, яка збільшила розмір коду на 10 байт. Можливо, є системи, де такої ситуації не виникає, тому мій код може бути оцінений на 10 байт менше. Не знаю.
  • Завдяки підказкам з гольфу на C , зокрема, немає типу повернення для основного , EOF поводження та потрійного оператора , останнього для того, щоб вказати, що ?:може вкладатись ,без (…).

Ви можете пересуватися int c, m, f[3];назовні main, щоб уникнути декларування типів. Для глобальних змінних вам не потрібно декларувати int. Але загалом цікаве рішення.
Конрад Боровський

розвага з fork () на windows. хе

Це не працює для мене. Команди без виведення труби двічі і yes|head -3продовжують працювати назавжди, і оболонка залишається після кожної команди. Я використовую gcc версії 4.6.3 (Ubuntu / Linaro 4.6.3-1ubuntu5) без будь-яких комутаторів.
Денніс

@Dennis: Дякую за звіт. Неправильне використання потрійного оператора. Я мав би виконати тести одиниць перед тим, як вставити, але я був впевнений у цьому… Виправлено зараз ціною ще одного байта.
MvG

Зараз це добре працює. Я думаю, що ви можете позбутися ще 4-х байт: 2, визначивши макрос #define B break;case( break;попереднє defaultстає )B-1:) і 2, замінивши case'\n'і case'\'') на case 10і case 39.
Денніс

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

Звичайно, але після перечитання запитання я думаю, що це не порушує жодного правила (Ні система, ні аргумент, ні евал, джерело чи крапка ...)
Ф. Хаурі

Так, але цікаво: Використовуючи відокремлений і невидимий сеанс, щоб виконати всю роботу, ніж перед виходом вивантажувати всю історію на початковій консолі.
Ф. Хаурі

Я нормально з цим порушенням правил. На мій погляд, це досить розумно - і питання дозволяє розумним зловживанням правилом. +1 від мене.
Конрад Боровський

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 ;

Ви також можете записати програму як ще коротший однокласник в 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 від мене - я просто ніколи не подумав би про це.
Конрад Боровський

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: оболонка тупо чекає першої команди перед запуском третьої команди в тому ж конвеєрі. Коли труби заповнені, оболонка переходить у глухий кут. Тут оболонка не починається дд, поки крик не вийде, але крик чекає кота, а кішка чекає шкаралупу. Якщо ви вб'єте крикуна (можливо, 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.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.