实现shell脚本的子集


12

这个站点存在很多问题,涉及在标签中实现各种语言。但是,实际上所有这些都是深奥的语言,没有人使用。是时候为这里的大多数用户所知道的实用语言做翻译了。是的,如果您在阅读标题时遇到问题(不是您遇到的问题),则它是shell脚本。(是的,我故意挑战了,因为我对GolfScript和Befunge之类的语言感到无聊,所以我提出了一些挑战,即更实用的编程语言有更大的获胜机会)

但是,shell脚本是一种比较大的语言,因此我不会要求您实现它。相反,我将制作一小部分的Shell脚本功能。

我决定的子集是以下子集:

  • 执行程序(但是,即使允许使用单引号,程序也只能包含字母)
  • 程序参数
  • 单引号(接受任何可打印的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

除非您没有命令的任何参数,否则不允许您执行shell本身(此异常是为Perl编写的,当仅将参数放入时,它将在shell中运行命令system,但可以随意将其用于其他对象语言,如果您可以通过节省字符的方式执行此操作),或者您运行的命令是shell本身。这可能是此挑战中的最大问题,因为许多语言都有system执行shell的功能。而是使用直接调用程序的语言API,例如subprocessPython中的模块。无论如何,这对于安全性来说是个好主意,那么,您不想创建一个不安全的shell,您想要吗?这很可能会停止PHP,但是仍然有其他语言可供选择。

如果你想使你的程序在shell脚本,你不能使用evalsource.(如,一个功能,而不是一个字符)。我认为这将使挑战变得太容易了。

允许滥用规则。我明确禁止了很多事情,但是我几乎可以肯定,您仍然可以做我没有做过的事情。有时候,我对人们如何解释我的规则感到惊讶。另外,请记住,您可以为我未提及的任何事情做任何事情。例如,如果我尝试使用变量,则可以擦除硬盘(但请不要这样做)。

最短的代码获胜,因为这是codegolf。


管道...为什么一定要管道...
JB 2014年

1
@JB:我认为没有管道的Shell脚本不是Shell脚本,因为UNIX Shell中的代码流基于管道。
Konrad Borowski14年

我同意。我仍然认为这是实施挑战中最痛苦的部分。
JB 2014年

@JB我同意;我跳过了这个。
Timtech

4
我的意思是我完全跳过了挑战。
Timtech

Answers:


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 Borowski14年

@xfix非常感谢!我真的很喜欢这个挑战:-)
tecywiz121 2014年

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调用失败(例如由于程序名称错误),可能会发生任何事情。然后,我们有两个进程同时在充当shell。
  • 特殊字符“ |” 和换行符在加引号的字符串中保留其特殊含义。这违反了要求,因此我正在研究解决此问题的方法。 已修复,费用约为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。但总的来说,有趣的解决方案。
Konrad Borowski

在Windows上使用fork()很有趣。嘿

这对我不起作用。不带管道的命令输出两次,并且yes|head -3一直持续下去,并且每执行一次命令,shell就会退出。我使用的是gcc版本4.6.3(Ubuntu / Linaro 4.6.3-1ubuntu5),没有任何开关。
丹尼斯

@丹尼斯:感谢您的报告。三元运算符的使用不正确。我应该在粘贴之前进行单元测试,但是我确定……现在修复了,但又花了一个字节。
MvG 2014年

现在工作正常。我认为您可以减少4个字节:2通过定义宏#define B break;casebreak;之前default变为)B-1:)和2通过将case'\n'and 替换case'\''case 10and 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 2014年

当然,但是在重新阅读了问题之后,我认为这不会违反任何规则(没有系统,没有参数,没有评估,来源或句号……)
F. Hauri 2014年

是的,但在一个interresting方式:使用分离无形的会议做全程工作,比,在退出之前,倾倒初始控制台上的整个历史。
F. Hauri 2014年

我对此规则的滥用表示满意。我认为这足够聪明-这个问题允许聪明地滥用规则。向我+1。
Konrad Borowski14年

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-我永远都不会想到这一点。
Konrad Borowski14年

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>

这个shell做一些愚蠢的事情。使用启动一个交互式外壳程序perl shell.pl并尝试:

  • ls因为标准输出不是终端,所以打印在一列中。Shell将标准输出重定向到管道并从管道读取。
  • perl -E 'say "hi"; sleep 1' 等待1秒说声“嗨”,因为shell延迟了输出。
  • dd读取0字节,除非它是对此shell的第一个命令。对于第一个管道之后的每个管道,Shell将从一个空管道重定向标准输入。
  • 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: shell愚蠢地等待第一个命令,然后在同一管道中启动第三个命令。当管道装满时,外壳进入​​死锁状态。在这里,直到尖叫者退出,外壳才开始dd,但是尖叫者等待cat,而cat等待外壳。如果您杀死了尖叫者(也许pkill -f screamer在另一个外壳中),那么外壳将继续。
  • perl -e 'fork and exit; $0 = sleeper; sleep' 挂壳!
    • 错误#2: Shell等待管道中的最后一条命令关闭输出管道。如果命令退出而不关闭管道,则外壳继续等待。如果您杀死了卧铺,那么外壳将继续。
  • 'echo $((2+3))'在/ bin / sh中运行命令。这是带有一个参数的Perl的execsystem的行为,但前提是该参数包含特殊字符。

非高尔夫版本

#!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.
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.