Почему EOF в середине аргументов?

20

Я хотел написать небольшую функцию bash, чтобы я мог сказать bash, import osили from sys import stdoutона создаст новый интерпретатор Python с импортированным модулем.

Последняя fromфункция выглядит так:

from () {
    echo "from $@" | xxd
    python3 -i -c "from $@"
}

Если я назову это:

$ from sys import stdout
00000000: 6672 6f6d 2073 7973 2069 6d70 6f72 7420  from sys import 
00000010: 7374 646f 7574 0a                        stdout.
  File "<string>", line 1
    from sys
           ^
SyntaxError: invalid syntax
>>> 

Байты в from sysэто

66 72 6f 6d 20 73 79 73 20
f  r  o  m     s  y  s    

Там нет EOF, но интерпретатор Python ведет себя так, как будто читает EOF. В конце потока есть новая строка, чего и следовало ожидать.

fromСестра, которая импортирует весь модуль Python, выглядит следующим образом и решает проблему путем очистки и обработки строки, а также из-за сбоя в несуществующих модулях.

import () {
  ARGS=$@
  ARGS=$(python3 -c "import re;print(', '.join(re.findall(r'([\w]+)[\s|,]*', '$ARGS')))")
  echo -ne '\0x04' | python3 -i
  python3 -c "import $ARGS" &> /dev/null
  if [ $? != 0 ]; then
    echo "sorry, junk module in list"
  else
    echo "imported $ARGS"
    python3 -i -c "import $ARGS"
  fi
}

Это решает проблему необъяснимого EOF в потоке, но я хотел бы понять, почему Python считает, что EOF существует.

Кот
источник

Ответы:

42

Таблица в этом ответе Stack Overflow (полученная из Bash Hackers Wiki ) объясняет, как расширяются различные переменные Bash:

Вы делаете python -i -c "from $@", что превращается в python -i -c "from sys" "import" "stdout", и -cпринимает только один аргумент, поэтому он запускает команду from sys. Вы хотите использовать $*, который будет расширяться python -i -c "from sys import stdout"(при условии, что $IFSне установлен или начинается с пробела).

Михаил Мрозек
источник
2
Спасибо за восстановление, так как это ценная информация :)
кошка
1
Я думаю, что это должен быть принятый ответ, так как на самом деле это решает проблему, другой, голосующий против, просто объясняет проблему, но не дает решений или альтернативных обходных путей
Ferrybig
Хороший ответ. Эта таблица на самом деле взята из Bash Hackers Wiki. Не могли бы вы добавить правильную атрибуцию и убедиться, что у вас есть право на распространение?
Легкость гонок с Моникой
22

straceкак всегда покажет что происходит:

bash-4.1$ echo $$
3458

И в другом месте (или вы могли бы выяснить, как strace bash ...вызов функции):

bash-4.1$ strace -ff -o blah -p 3458

И обратно в эту первую оболочку:

bash-4.1$ from sys import stdout
  File "<string>", line 1
    from sys
           ^
SyntaxError: invalid syntax
>>> 
bash-4.1$ 

А потом обратно в straceоболочку:

Process 3458 attached
Process 25224 attached
^CProcess 3458 detached
bash-4.1$ grep exec blah.*
blah.25224:execve("/usr/bin/python", ["python", "-i", "-c", "from sys", "import", "stdout"], [/* 54 vars */]) = 0

Таким образом, фактический -cаргумент - -c "from sys"из-за того, как "$@"раскрывается, или из-за усеченной команды, на которой pythonдействует barfs.

thrig
источник
9

$@в двойных кавычках расширяется до списка элементов "$1" "$2" "$3"и т. д.

#!/bin/bash
expand () {
    for string in "from $@" ; do
        echo "$string"
    done
}

expand sys import stdout

Python ожидает, что код будет в одном аргументе, а не в серии аргументов.

choroba
источник
6

Python вызывается как

execve("/usr/bin/python", ["python", "-i", "-c", "from sys", "import", "stdout"], [/* 54 vars */])

(см . ответ Трига ).

Чтобы $@раскрыть как одну строку (при условии здравого смысла $IFS), вы можете использовать $*внутри двойных кавычек:

python3 -i -c "from $*"

Подтверждено с strace -e execve:

execve("/usr/bin/python", ["python", "-i", "-c", "from sys import stdout"], [/* 54 vars */]) = 0
Тоби Спейт
источник
2

Strace показывает, какие аргументы используются. Но самый простой способ увидеть, что именно обрабатывается, - это добавить printf '<%s> 'перед каждой соответствующей строкой и закрывать echo(генерировать как новую строку):

Таким образом, функция может быть изменена на это:

from () {
    printf '<%s> ' "from $@"; echo
    printf '<%s> ' python3 -i -c "from $@"; echo
}

И когда называется:

$ from sys import stdout
<from sys> <import> <stdout> 
<python3> <-i> <-c> <from sys> <import> <stdout>

Понятно, что «от sys» отправляется на python как один аргумент.
Это то, что получает Python, и Python действует «от sys».


источник