将短小的Python脚本嵌入Bash脚本中

78

我想在bash脚本中嵌入短的Python脚本文本,以便在.bash_profile等中使用。如何最好地实现这样的操作?

我目前的解决方案是使用-c选项调用Python解释器,并告诉解释器从stdin读取并exec它所读取的任何内容。从那里,我可以构建简单的工具,例如以下工具,使我能够处理文本以供交互提示使用:

function pyexec() {
    echo "$(/usr/bin/python -c 'import sys; exec sys.stdin.read()')"
}

function traildirs() {
    pyexec <<END
trail=int('${1:-3}')
import os
home = os.path.abspath(os.environ['HOME'])
cwd = os.environ['PWD']
if cwd.startswith(home):
    cwd = cwd.replace(home, '~', 1)
parts = cwd.split('/')
joined = os.path.join(*parts[-trail:])
if len(parts) <= trail and not joined.startswith('~'):
    joined = '/'+joined
print joined
END
}

export PS1="\h [\$(traildirs 2)] % "

我觉得这个方法有点可疑,想知道还有没有其他替代的方法。

我的bash脚本技能非常基础,所以我特别想知道从bash解释器的角度来看,我是否在做一些愚蠢的事情。


你能更清楚地说明你实际上想要做什么吗?从我看到的情况来看,Python并不是真正必需的。你可以用shell完成大多数事情。 - ghostdog74
1
@ghostdog74:没有比我说的更深入的东西;我只是一个比bash程序员更擅长python编程的人,而且在我看来,总的来说,python比bash更强大。在某些情况下,用python实现在bash脚本中使用的功能可能很方便,并且有时不需要依赖外部文件。经过15年后,我终于从tcsh转向bash,并尝试将shell弯曲成我的意愿/偏好。 - Matt Anderson
为什么不直接创建一个 .py 模块文件呢?如果有更好的解决方案,那何必强制使用 Python 脚本呢?通常最好的解决方案是完全停止使用 shell。 - S.Lott
3
在我的情况下,我正在将bash脚本作为Alfred命令运行,但需要Python的拆分功能。创建.py文件会增加不必要的开销。 - Chris Redford
9个回答

63

你为什么需要使用-c?以下方式对我有效:

python << END
... code ...
END

无需额外的任何内容。


4
为了将输出结果存储到一个变量中,我使用了ABC=$(python << END ... END)的方式,并且它起作用了。 - Evgeni Sergeev
1
如果我们想要从Bash脚本中传递一个字符串参数给Python,该怎么做呢? - Augustin Riedinger
2
那么你需要使用“-”,就像Ned的答案一样。参数会跟在破折号后面。 - Ricardo Cárdenes
8
注意如何命名heredoc标记,当它像你的例子一样未被引用时,shell扩展会在heredoc字符串中发生。例如,如果你的Python代码只包括print '$SHELL',输出将是/bin/bash或者你的shell所在的路径。如果你将第一行改成python - <<'END',输出将是$SHELL。如果你要复制和粘贴现有代码嵌入到Bash脚本中,你几乎肯定不想要Shell替换--你希望代码以与没有嵌入到Bash脚本中运行相同的方式运行,对吧? - Chris Johnson
1
如果你的所有内容都在一行中,就像看起来的那样,那是行不通的:https://dev59.com/xmMl5IYBdhLWcg3wJD9S - Ricardo Cárdenes
显示剩余3条评论

41

Python解释器接受命令行上的-作为stdin的同义词,因此您可以将对pyexec的调用替换为:

python - <<END

参见命令行参考此处


1
哦,我在 Python 手册上进行了文本搜索,但是没有找到“stdin”这个词。仔细一看,“标准输入”倒是有的。唉。 - Matt Anderson
同意使用heredoc方法可以起作用。但是需要注意以下几点:(1)如果heredoc标记(例如Ned的示例中的END)出现在Python脚本的第0列,则heredoc将提前结束;(2)heredoc标记的变体(带或不带前导-)会影响制表符和空格是否被保留;(3)当打开变量扩展时(heredoc标记未引用),Python和bash之间存在语法重叠,例如${1}可能会合法地出现在Python字符串格式说明符中,但如果heredoc标记未引用,则会被替换为bash命令行参数。 - Chris Johnson
1
小心命名heredoc标记。当像你的例子一样没有使用引号时,shell扩展会在heredoc字符串中发生。例如,如果你的python代码只包括print '$SHELL',输出将是/bin/bash或你的shell所在位置。如果你将第一行改为python - << 'END',输出将是$SHELL。如果你正在复制和粘贴现有的代码嵌入到bash脚本中,你几乎肯定不希望进行shell替换——你想让代码运行方式与它不嵌入bash脚本时一样,对吧? - Chris Johnson
2
最好提供一个完整的例子。 - kev
Ricardo Cárdenes在下面给出了一个更完整的例子,供您参考:https://dev59.com/knI95IYBdhLWcg3wtwRe#2189116 - micseydel
@Ned Deily:您能帮忙回答一下这个在stackoverflow上发布的问题吗?链接为https://stackoverflow.com/questions/68002623/how-to-pass-parameter-to-python-script-from-unix?noredirect=1 - codeholic24

30
使用bash的“here document”的一个问题是脚本会被传递到Python的stdin上,因此如果您想将Python脚本用作过滤器,则变得笨重。一种替代方法是使用bash的“process substitution”,类似于这样:
... | python <( echo '
code here
' ) | ...

如果脚本太长,你也可以在括号内使用here document,像这样:
... | python <(
cat << "END"
code here
END
 ) | ...

在脚本中,您可以像通常从/到标准输入/输出读写一样进行操作(例如,sys.stdin.readlines 以获取所有输入)。
此外,可以像其他答案中提到的那样使用 python -c,但是以下是我喜欢的格式化方式,同时仍然遵守 Python 的缩进规则(credits):
read -r -d '' script <<-"EOF"
    code goes here prefixed by hard tab
EOF
python -c "$script"

请确保这里文档内每行的第一个字符是一个硬制表符。如果您必须将其放在函数内部,则可以使用下面的技巧使其看起来对齐:

function somefunc() {
    read -r -d '' script <<-"----EOF"
        code goes here prefixed by hard tab
----EOF
    python -c "$script"
}

3
为什么没有赞?这是这里唯一正确的答案,如果你想在程序中使用sys.stdin! - Igor Chubin
这绝对是迄今为止最好的解决方案!我还要补充说,您可能希望在第二个例子中引用 END:cat <<'END' 以避免在 HERE 文件中转义 $ 符号和其他特殊字符。 - Michael Goldshteyn
@MichaelGoldshteyn 完成了! - haridsv
@haridsv:你能帮忙回答一下这个在stackoverflow上发布的问题吗:https://stackoverflow.com/questions/68002623/how-to-pass-parameter-to-python-script-from-unix?noredirect=1 - codeholic24
为了将命令行参数传递到脚本中,请更新为:python -c "$script" $@ - Contango
显示剩余2条评论

9
有时候使用文档注释并不是个好主意。另一种选择是使用python -c命令:
py_script="
import xml.etree.cElementTree as ET,sys
...
"

python -c "$py_script" arg1 arg2 ...

简单易懂,可以让你把 Python 代码从其他 Bash 代码中分离出来。同时也可以让你的 Python 代码从标准输入读取数据。 - dbort
@Don Li,您能帮忙解答一下这个贴在Stack Overflow上的问题吗?链接是:https://stackoverflow.com/questions/68002623/how-to-pass-parameter-to-python-script-from-unix?noredirect=1 - codeholic24

7
如果您需要在Bash脚本中使用Python的输出,可以按照以下方式操作:
#!/bin/bash

ASDF="it didn't work"

ASDF=`python <<END
ASDF = 'it worked'
print ASDF
END`

echo $ASDF

1
注意如何命名heredoc标记。当它像你的例子一样未被引用时,shell扩展将发生在heredoc字符串中。例如,如果你的python代码只包含print '$SHELL',输出将是/bin/bash或者你的shell所在位置。如果你将第一行改为python - <<'END',输出将是$SHELL。如果你正在复制和粘贴现有的代码嵌入到bash脚本中,你几乎肯定不想要shell替换——你希望代码运行的方式与它不嵌入bash脚本时一样,对吧? - Chris Johnson
@desgua:你能帮忙回答一下这个在stackoverflow上发布的问题吗:https://stackoverflow.com/questions/68002623/how-to-pass-parameter-to-python-script-from-unix?noredirect=1 - codeholic24

6

准备好复制粘贴的输入示例:

input="hello"
output=`python <<END
print "$input world";
END`

echo $output

你能帮忙回答这个在stackoverflow上发布的问题吗:https://stackoverflow.com/questions/68002623/how-to-pass-parameter-to-python-script-from-unix?noredirect=1 - codeholic24
@AhHatem:你能帮忙解决一下这个问题吗?它是在stackoverflow上发表的:https://stackoverflow.com/questions/68002623/how-to-pass-parameter-to-python-script-from-unix?noredirect=1 - codeholic24

2
尝试这个:

#!/bin/bash
a="test"
python -c "print  '$a this is a test'.title()"

2
有趣……我现在也想要答案;-)
他不是在问如何在bash脚本中执行Python代码,而是实际上设置Python环境变量。
将以下内容放入bash脚本中,尝试让它说“它运行了”。
export ASDF="it didn't work"

python <<END
import os
os.environ['ASDF'] = 'it worked'
END

echo $ASDF

问题在于Python在环境的副本中执行。当Python退出后,对环境所做的任何更改都不会被看到。
如果有解决方案,我也很想看到它。

1
这并不是我想要的,而且我相信你不能直接从子进程获取环境变量或更改父进程的环境变量。但是,你可以在bash脚本中eval Python脚本的输出,从而执行任意语句,设置环境变量或进行其他任何操作。 - Matt Anderson
1
@Eric:如果你真的想要一个答案,就发表你自己的问题。 - Ned Deily
马特,抱歉...我简要地看了一下你的Python脚本,看到你在处理os.environ相关的内容,就以为那是你要做的事情。我错了。 - eric.frederich
你可以通过结合bash变量和python输出来管理它。请看下面我的答案。 - desgua
@eric.frederich,你能帮忙回答这个在stackoverflow上发布的问题吗:https://stackoverflow.com/questions/68002623/how-to-pass-parameter-to-python-script-from-unix?noredirect=1 - codeholic24

1
如果您使用的是zsh,可以通过zsh的join和python标准输入选项(python -)在脚本中嵌入Python:
py_cmd=${(j:\n:)"${(f)$(
    # You can also add comments, as long as you balance quotes
    <<<'if var == "quoted text":'
    <<<'  print "great!"')}"}

catch_output=$(echo $py_cmd | python -)

你可以缩进 Python 代码片段,这样在函数内部时会比 EOF<<END 解决方案看起来更加美观。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接