シェルスクリプトのサブセットを実装する


12

このサイトには、タグでのさまざまな言語の実装に関連する多くの問題がありました。ただし、実際にはそれらはすべて、誰も使用しない難解な言語でした。ここのユーザーのほとんどがおそらく既に知っている実用的な言語の通訳を作成する時間です。はい、タイトルの読みに問題がある場合(あなたが持っているのではない)のシェルスクリプトです。(はい、GolfScriptやBefungeなどの言語がすべてを獲得するのにうんざりしているので、私は意図的にこの挑戦をしました。そのため、より実用的なプログラミング言語が勝つ可能性が高い場合に挑戦をしました)

ただし、シェルスクリプトは比較的大きな言語であるため、実装を求めません。代わりに、シェルスクリプト機能の小さなサブセットを作成します。

私が決めたサブセットは次のサブセットです。

  • プログラムの実行(ただし、単一引用符が許可されている場合でも、プログラムには文字のみが含まれます)
  • プログラム引数
  • 単一引用符(空白を含む印刷可能なASCII文字を受け入れ、単一引用符を除く)
  • 引用符で囲まれていない文字列(ASCII文字、数字、ダッシュを許可)
  • パイプ
  • 空のステートメント
  • 改行で区切られた複数のステートメント
  • 末尾/先頭/複数のスペース

このタスクでは、STDINから入力を読み取り、要求されたすべてのコマンドを実行する必要があります。POSIX互換のオペレーティングシステムを安全に想定できるため、Windowsなどの移植性は必要ありません。他のプログラムにパイプされていないプログラムは、STDINから読み取らないと安全に想定できます。コマンドが存在すると仮定しても安全です。他に何も使用されないと想定しても安全です。何らかの安全な仮定が破られていれば、何でもできます。最大15個の引数と512文字未満の行を安全に想定できます(明示的なメモリ割り当てなどが必要な場合-Cがまだ小さい場合でも、Cで勝つ可能性はほとんどありません)。ファイル記述子をクリーンアップする必要はありません。

すべての行を受信した後、またはSTDINが終了した後でも、いつでもプログラムを実行できます。必要なアプローチを選択してください。

シェルをテストできるシンプルなテストケース(3番目のコマンドの後に続く空白に注意してください):

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シェルを実行する関数があるため、これはおそらくこの課題における最大の問題です。代わりにsubprocess、Pythonのモジュールのようなプログラムを直接呼び出す言語APIを使用します。とにかく、これはセキュリティにとっては良い考えです。そして、安全でないシェルを作成したくないでしょうか?これはおそらくPHPを停止しますが、とにかく選択する他の言語があります。

あなたはシェルスクリプトでプログラムを作成しようとしている場合は、使用を許可されていないevalsourceまたは.(機能、いない文字、のように)。私の意見では、それは挑戦をあまりにも簡単にするでしょう。

巧妙なルールの乱用が許可されています。私が明示的に禁止したことはたくさんありますが、あなたが私が許可していないことをまだ許可されていると確信しています。私のルールを人々がどのように解釈しているかに驚かされることがあります。また、私が言及していないことは何でもできるということを忘れないでください。たとえば、変数を使用しようとすると、ハードディスクを消去できます(ただし、しないでください)。

これがcodegolfであるため、最短のコードが優先されます。


それはパイプである必要はありWhy'dパイプ... ...
JB

1
@JB:UNIXシェルのコードフローはパイプに基づいているため、パイプラインのないシェルスクリプトはシェルスクリプトではありません。
コンラッドボロスキ14年

同意する。私は今でも、実装するのが最も苦痛な部分であると考えています。
JB

@JB同意します。これはスキップします。
ティムテック14年

4
つまり、チャレンジを完全にスキップするということです。
ティムテック14年

回答:


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

これは素晴らしいですね。実行可能な最適化がいくつかあります(前に空白を削除するなど*)が、それ以外は見栄えが良い:-)。新しいメンバーが難しい問題に対してこのような優れたソリューションを作成したことに驚いています。
コンラッド

@xfixどうもありがとう!私は本当にこの挑戦を楽しんだ:
tecywiz121 14年

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呼び出しが失敗した場合、たとえばプログラム名の入力ミスが原因で、何かが起こる可能性があります。次に、2つのプロセスが同時にシェルで実行されます。
  • 特殊文字「|」改行は、引用符で囲まれた文字列内で特別な意味を保持します。これは要件に違反しているため、これを修正する方法を調査しています。 固定、約11バイトのコスト。

その他の注意事項

  • 明らかに単一のヘッダーが含まれていないため、使用されるすべての関数の暗黙的な宣言に依存します。呼び出し規約に応じて、これは問題になる場合とそうでない場合があります。
  • 最初echo 'foo bar baz' | sed 's/bar/BAR/' | sed 's/baz/zap/'はハングしたバグがありました。問題は明らかに閉じられていない書き込みパイプであったため、そのクローズコマンドを追加する必要があり、コードサイズが10バイト増加しました。おそらく、このような状況が発生しないシステムがあるため、私のコードの評価は10バイト少なくなります。知りません。
  • おかげCのゴルフのヒント、特にノーリターン型の主のためにEOF取扱い及び三項演算子、それを指摘して、最後の1が?:入れ子になっていることができ,ずに(…)

型の宣言を避けるために、int c, m, f[3];外側mainに移動できます。グローバル変数の場合、宣言する必要はありませんint。しかし、一般的に、興味深いソリューション。
コンラッドボロスキー14年

Windowsのfork()をお楽しみください。heh

これは私のために働いていません。パイプのないコマンドは2回出力され、yes|head -3永久に継続し、コマンドは1つ実行されるたびに終了します。スイッチなしでgccバージョン4.6.3(Ubuntu / Linaro 4.6.3-1ubuntu5)を使用しています。
デニス14年

@Dennis:レポートをありがとう。三項演算子の誤った使用。貼り付ける前に単体テストを実行する必要がありましたが、確信が持てました…もう1バイトのコストで修正されました。
MvG 14年

今は正常に動作します。もう4バイトを削ることができると思います。2つはマクロを定義することで#define B break;casebreak;前はにdefaultなります)B-1:)、2はcase'\n'and case'\''case 10andで置き換えることでできcase 39ます。
デニス14年

3

bash(+ screen)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 14年

はい、しかしinterrestingの方法では:使用してデタッチ目に見えない終了する前に、最初のコンソール上の全体の歴史をダンプ、より、全体の仕事をするためのセッション。
F.ハウリ14年

私はこのルールの乱用に大丈夫です。私の意見では十分に賢いです-そして、質問は巧妙なルールの乱用を可能にします。私から+1。
コンラッドボロスキー14年

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-私はこれを考えたことがありません。
コンラッドボロスキー14年

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標準出力は端末ではないため、1列に印刷されます。シェルは標準出力をパイプにリダイレクトし、パイプから読み取ります。
  • 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:シェルは、同じパイプラインで3番目のコマンドを開始する前に、最初のコマンドを愚かに待機します。パイプがいっぱいになると、シェルはデッドロックに入ります。ここでは、スクリーマーが終了するまでシェルはddを開始しませんが、スクリーマーは猫を待ち、猫はシェルを待ちます。スクリーマーを(おそらくpkill -f screamer別のシェルで)殺すと、シェルは再開します。
  • perl -e 'fork and exit; $0 = sleeper; sleep' シェルがハングします!
    • バグ#2:シェルは、パイプラインの最後のコマンドが出力パイプを閉じるのを待ちます。パイプを閉じずにコマンドが終了すると、シェルは待機を続けます。寝台車を殺すと、シェルが再開します。
  • 'echo $((2+3))'/ bin / shでコマンドを実行します。これは、引数に特殊文字が含まれている場合にのみ、1つの引数を持つ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.