21

bashでREPL (read-eval-print loop)を実装しようとしています。そのようなものがすでに存在する場合は、以下を無視して、この質問にそのポインタを付けて回答してください。

このスクリプトを例として使用しましょう (名前を付けますtest.sh):

if true
then
  echo a
else
  echo b
fi
echo c

私がやりたいのは、このスクリプトを 1 行ずつ読んで、これまでに読んだものが完全な bash 式であるかどうかを確認することです。それが完了している場合、evalそれ; それ以外の場合は、次の行を読み続けます。以下のスクリプトは、うまくいけば私のアイデアを示しています (ただし、完全には機能しません)。

x=""
while read -r line
do
  x=$x$'\n'$line  # concatenate by \n
  # the line below is certainly a bad way to go
  if eval $x 2>/dev/null; then
    eval $x  # code seems to be working, so eval it
    x=""  # empty x, and start collecting code again
  else
    echo 'incomplete expression'
  fi
done < test.sh

動機

bash スクリプトの場合、それを構文的に完全な式に解析し、各式を評価し、出力をキャプチャし、最後にソース コードと出力をマークアップします (たとえば、Markdown/HTML/LaTeX/... を使用します)。たとえば、スクリプトの場合

echo a
echo b

私が達成したいのは、次のような出力です。

```bash
echo a
```

```
a
```

```bash
echo b
```

```
b
```

スクリプト全体を評価してすべての出力を取得する代わりに:

```bash
echo a
echo b
```

```
a
b
```
4

4 に答える 4

6
bash -n -c "$command_text"

...$command_text実際に実行せずに、構文的に有効なスクリプトであるかどうかを判断します。


「構文的に有効」と「正しい」の間には、非常に広いスペースがあることに注意してください。言語を適切に解析したい場合は、 http://shellcheck.net/のようなものを採用することを検討してください。

于 2016-07-25T02:49:17.800 に答える
4

次のスクリプトは、期待どおりの Markdown 出力を生成するはずです。

eval "set -n; $x"コマンドの構文エラーをチェックして、コマンドが完全かどうかを確認するために使用されます。構文エラーのないコマンドのみが完了したと見なされ、実行され、出力 Markdown に表示されます。

処理される入力スクリプトはサブシェルで実行されるため、処理スクリプト自体に干渉しないことに注意してください (つまり、入力スクリプトは処理スクリプトと同じ変数名を使用でき、変数の値を変更することはできません)。処理スクリプト内)。唯一の例外は、 と呼ばれる特殊変数です___internal__variable___

それを達成する方法には2 つのアプローチがありますが、それを以下に示します。バージョン 1では、新しい完全なコマンドが処理されるたびに、それより前のすべてのステートメントが実行され、コマンドの「コンテキスト」が作成されます。これにより、入力スクリプトが効果的に複数回実行されます。

バージョン 2では、完全なコマンドが実行されるたびに、サブシェルの環境が変数に格納されます。その後、次のコマンドが実行される前に、以前の環境がサブシェルで復元されます。

バージョン 1

#!/bin/bash

x=""  # Current
y=""  # Context
while IFS= read -r line  # Keep indentation
do
    [ -z "$line" ] && continue  # Skip empty lines

    x=$x$'\n'$line  # Build a complete command
    # Check current command for syntax errors
    if (eval "set -n; $x" 2> /dev/null)
    then
        # Run the input script up to the current command
        # Run context first and ignore the output
        ___internal_variable___="$x"
        out=$(eval "$y" &>/dev/null; eval "$___internal_variable___")
        # Generate command markdown
        echo "=================="
        echo
        echo "\`\`\`bash$x"
        echo "\`\`\`"
        echo
        # Generate output markdown
        if [ -n "$out" ]
        then
            echo "Output:"
            echo
            echo "\`\`\`"
            echo "$out"
            echo "\`\`\`"
            echo
        fi
        y=$y$'\n'$line  # Build context
        x=""  # Clear command
    fi
done < input.sh

バージョン 2

#!/bin/bash

x=""  # Current command
y="true"  # Saved environment
while IFS= read -r line  # Keep indentation
do
    [ -z "$line" ] && continue  # Skip empty lines

    x=$x$'\n'$line  # Build a complete command
    # Check current command for syntax errors
    if (eval "set -n; $x" 2> /dev/null)
    then
        # Run the current command in the previously saved environment
        # Then store the output of the command as well as the new environment
        ___internal_variable_1___="$x"  # The current command
        ___internal_variable_2___="$y"  # Previously saved environment
        out=$(bash -c "${___internal_variable_2___}; printf '<<<BEGIN>>>'; ${___internal_variable_1___}; printf '<<<END>>>'; declare -p" 2>&1)
        # Separate the environment description from the command output
        y="${out#*<<<END>>>}"
        out="${out%%<<<END>>>*}"
        out="${out#*<<<BEGIN>>>}"

        # Generate command markdown
        echo "=================="
        echo
        echo "\`\`\`bash$x"
        echo "\`\`\`"
        echo
        # Generate output markdown
        if [ -n "$out" ]
        then
            echo "Output:"
            echo
            echo "\`\`\`"
            echo "$out"
            echo "\`\`\`"
            echo
        fi
        x=""  # Clear command
    fi
done < input.sh

入力スクリプトの場合input.sh:

x=10
echo "$x"
y=$(($x+1))
echo "$y"


while [ "$y" -gt "0" ]
do
    echo $y
    y=$(($y-1))
done

出力は次のようになります。

==================

```bash
x=10
```

==================

```bash
echo "$x"
```

Output:

```
10
```

==================

```bash
y=$(($x+1))
```

==================

```bash
echo "$y"
```

Output:

```
11
```

==================

```bash
while [ "$y" -gt "0" ]
do
    echo $y
    y=$(($y-1))
done
```

Output:

```
11
10
9
8
7
6
5
4
3
2
1
```
于 2016-07-25T04:43:36.690 に答える