Реализовать подмножество сценария оболочки

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или .(как в функции, а не символ). Это сделало бы задачу слишком легкой, на мой взгляд.

Разумное нарушение правил допускается. Я явно запретил многие вещи, но я почти уверен, что вам все еще разрешено делать то, чего я не делал. Иногда я удивляюсь тому, как люди интерпретируют мои правила. Кроме того, помните, что вы можете сделать все, что я не упомянул. Например, если я пытаюсь использовать переменные, вы можете стереть жесткий диск (но, пожалуйста, не надо).

Самый короткий код выигрывает, так как это Codegolf.

Конрад Боровски
источник
Трубы ... Почему это должны быть трубы ...
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(),
tecywiz121
источник
Это выглядит великолепно. Есть некоторые оптимизации, которые могут быть сделаны (например, удаление пробелов ранее *), но в остальном это выглядит великолепно :-). Я удивлен, что новый участник сделал такое хорошее решение для сложной проблемы.
Конрад Боровски
@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-манипулятора и троичного оператора , последний для указания того, что ?:может быть вложенным ,без (…).
MVG
источник
Вы можете выйти int c, m, f[3];наружу main, чтобы избежать объявления типов. Для глобальных переменных вам не нужно объявлять int. Но в общем, интересное решение.
Конрад Боровски
весело с fork () на окнах. хех
Это не работает для меня. Команды без конвейера выводятся дважды и yes|head -3продолжают работать вечно, а оболочка завершается после каждой отдельной команды. Я использую gcc версии 4.6.3 (Ubuntu / Linaro 4.6.3-1ubuntu5) без каких-либо переключателей.
Деннис
@ Денис: Спасибо за отчет. Неправильное использование троичного оператора. Я должен был выполнить модульные тесты перед вставкой, но я был так уверен ... Исправлено сейчас, за счет еще одного байта.
MvG
Теперь работает нормально. Я думаю, что вы можете отнять еще 4 байта: 2, определив макрос #define B break;case( break;перед defaultстановится )B-1:), и 2, заменив case'\n'и case'\'') на case 10и case 39.
Деннис
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
Конечно, но после перечитывания вопроса, я думаю, что это не нарушает никаких правил (без системы, без аргументов, без оценки, источника или точки ...)
Ф. Хаури
Да, но интересным способом: используя отдельный и невидимый сеанс для выполнения всей работы, чем перед выходом сбросить всю историю на исходную консоль.
Ф. Хаури
Я в порядке с этим правилом злоупотребления. На мой взгляд, это достаточно умно - и вопрос позволяет разумно злоупотреблять правилами. +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: оболочка тупо ждет первой команды, прежде чем запустить третью команду в том же конвейере. Когда трубы заполнены, снаряд заходит в тупик. Здесь оболочка не запускает dd до тех пор, пока не закроется screamer, но screamer ждет cat, а cat ждет shell. Если вы убьете screamer (возможно, pkill -f screamerв другой оболочке), то оболочка возобновится.
  • perl -e 'fork and exit; $0 = sleeper; sleep' вешает раковину!
    • Ошибка № 2: оболочка ожидает последней команды в конвейере, чтобы закрыть выходной канал. Если команда завершается без закрытия канала, оболочка продолжает ждать. Если убить спящего, то оболочка возобновится.
  • 'echo $((2+3))'запускает команду в / bin / sh. Это поведение exec и системы 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.
kernigh
источник