Подтвердить что ты не робот

Есть ли способ проверить, завершен ли bash script или нет?

Я пытаюсь реализовать REPL (цикл чтения-eval-print) в bash. Если такая вещь уже существует, пожалуйста, проигнорируйте следующее и ответьте на этот вопрос указателем на нее.

Позвольте использовать этот script в качестве примера (name it test.sh):

if true
then
  echo a
else
  echo b
fi
echo c

Что я хочу сделать, так это прочитать эту script строку за строкой, проверить, что то, что я прочитал до сих пор, является полным выражением bash; если он завершен, eval it; в противном случае продолжайте читать следующую строку. script ниже иллюстрирует мою идею, надеюсь (это не совсем работает, хотя).

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 script, я хочу разобрать его в синтаксически полные выражения, оценить каждое выражение, захватить вывод и, наконец, пометить исходный код и выводить (скажем, используя Markdown/HTML/LaTeX/...). Например, для script

echo a
echo b

То, что я хочу достичь, - это результат следующим образом:

```bash
echo a
```

```
a
```

```bash
echo b
```

```
b
```

вместо оценки всего script и захватить весь вывод:

```bash
echo a
echo b
```

```
a
b
```
4b9b3361

Ответ 1

bash -n -c "$command_text"

... определит, является ли ваш $command_text синтаксически допустимым script, фактически не выполнив его.


Обратите внимание, что существует огромная ширина пространства между "синтаксически действительными" и "правильными". Подумайте о том, чтобы принять что-то вроде http://shellcheck.net/, если вы хотите правильно разобрать язык.

Ответ 2

Следующие сценарии должны генерировать вывод Markdown, который вы ожидаете.

eval "set -n; $x" используется для проверки завершения команды, путем проверки наличия синтаксических ошибок в команде. Только команда, которая не имеет синтаксических ошибок, будет считаться полной, выполнена и показана на выходе Markdown.

Обратите внимание, что вход script, который должен быть обработан, выполняется в под-оболочке и поэтому не будет мешать самой обработке script (т.е. вход script может использовать те же имена переменных, что и script и не может изменять значения переменных в обработке script). Единственным исключением являются специальные переменные, называемые ___internal__variable___.

Существует два подхода к тому, как достичь этого, что представлено ниже. В Версия 1, всякий раз, когда обрабатывается новая полная команда, все выполняемые перед ней операторы выполняются для создания "контекста" для команды. Это эффективно запускает вход script несколько раз.

В Версия 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

Пример

Для ввода script 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
```

Ответ 3

Предположим, что ваши тестовые команды хранятся в файле с именем "example". То есть, используя те же команды, что и в предыдущем ответе:

$ cat example
x=3
echo "$x"
y=$(($x+1))
echo "$y"


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

команда:

$ (echo 'PS1=; PROMPT_COMMAND="echo -n =====; echo"'; cat example2 ) | bash -i

дает:

=====
x=3
=====
echo "$x"
3
=====
y=$(($x+1))
=====
echo "$y"
4
=====

=====

=====
while [ "$y" -gt "0" ]
> do
>     echo $y
>     y=$(($y-1))
> done
4
3
2
1
=====
exit

если вам также интересны промежуточные результаты цикла, команда:

$ ( echo 'trap '"'"'echo; echo command: $BASH_COMMAND; echo answer:'"'"' DEBUG'; cat example ) | bash

приводит к:

command: x=3
answer:

command: echo "$x"
answer:
3

command: y=$(($x+1))
answer:

command: echo "$y"
answer:
4

command: [ "$y" -gt "0" ]
answer:

command: echo $y
answer:
4

command: y=$(($y-1))
answer:

command: [ "$y" -gt "0" ]
answer:

command: echo $y
answer:
3

command: y=$(($y-1))
answer:

command: [ "$y" -gt "0" ]
answer:

command: echo $y
answer:
2

command: y=$(($y-1))
answer:

command: [ "$y" -gt "0" ]
answer:

command: echo $y
answer:
1

command: y=$(($y-1))
answer:

command: [ "$y" -gt "0" ]
answer:

Добавление 1

Нетрудно изменить предыдущие результаты в другом формате. Например, этот маленький perl script:

$ cat formatter.pl 
#!/usr/bin/perl
#

$state=4; # 0: answer, 1: first line command, 2: more command, 4: unknown

while(<>) {
#  print $state;

  if( /^===COMMAND===/ ) {
    print "===\n";
    $state=1;
    next;
  }

  if( $state == 1 ) {
    print;
    $state=2;
    next;
  }

  if( $state == 2 && /^>+ (.*)/ ) {
    print "$1\n";
    next;
  }

  if( $state == 2 ) {
    print "---\n";
    $state=0;
    redo;
  }

  if( $state == 0 ) {
    print;
    next;
  }
}

при использовании в команде:

( echo 'PS1="===COMMAND===\n"'; cat example ) | bash -i 2>&1 | ./formatter.pl

дает следующий результат:

===
x=3
===
echo "$x"
---
3
===
y=$(($x+1))
===
echo "$y"
---
4
===

===

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

Ответ 4

Вместо pidfiles, если ваш script имеет уникально идентифицируемое имя, вы можете сделать что-то вроде этого:

  #!/bin/bash
COMMAND=$0
# exit if I am already running
RUNNING=`ps --no-headers -C${COMMAND} | wc -l`
if [ ${RUNNING} -gt 1 ]; then
  echo "Previous ${COMMAND} is still running."
  exit 1
fi
... rest of script ...