【问题标题】:Read a python variable in a shell script?在 shell 脚本中读取 python 变量?
【发布时间】:2016-01-12 19:12:16
【问题描述】:

我的 python 文件有这两个变量:

week_date = "01/03/16-01/09/16"
cust_id = "12345"

如何将其读入包含这两个变量的 shell 脚本?

我当前的 shell 脚本需要手动编辑“dt”和“id”。我想将python变量读入shell脚本,这样我就可以编辑我的python参数文件而不是那么多文件。

外壳文件:

#!/bin/sh

dt="01/03/16-01/09/16" 
cust_id="12345"

在一个新的 python 文件中,我可以只导入参数 python 文件。

【问题讨论】:

  • 他们设置在哪里?在模块级别?
  • 它在模块级别
  • 顺便说一句,使用 #!/bin/sh shebang 或使用 sh scriptname 调用它会使您的脚本成为 POSIX sh 脚本,而不是 bash 脚本。由于您的问题被标记为 bash,我假设这是一个错误并且您打算使用 bash。

标签: python bash shell


【解决方案1】:

其他答案提供了一种完全按照您的要求进行操作的方法,但我认为这个想法有点疯狂。有一种更简单的方法可以满足这两个脚本 - 将这些变量移动到配置文件中。您甚至可以保留简单的分配格式。

自行创建配置:(ini-style)

dt="01/03/16-01/09/16"
cust_id="12345"

在python中:

config_vars = {}
with open('the/file/path', 'r') as f:
    for line in f:
        if '=' in line:
            k,v = line.split('=', 1)
            config_vars[k] = v
week_date = config_vars['dt']
cust_id = config_vars['cust_id']

在 bash 中:

source "the/file/path"

而且您不再需要进行疯狂的源代码解析。或者,您可以只使用 json 作为配置文件,然后在 python 中使用 json 模块,在 shell 中使用 jq 进行解析。

【讨论】:

  • 我唯一可能会改变的就是——如果你要让 Python 代码解析用 shell 编写的东西,考虑使用shlex 来真正获得 shell 兼容(好吧,POSIX-sh - 兼容;它不支持 bash 扩展,例如 $'') lexing;这样您就可以获得对多行分配等的支持。
  • 所以我想配置是 ini 风格,而不是 bash 风格(否则我们又回到了疯狂的“解析其他语言”问题)。如果您需要转义,多行或其他任何内容,只需使用 json 代替...
  • 类似:import shlex; config_vars = dict([item.split('=', 1) for item in shlex.split(open('the/file/path').read()) if '=' in item]);这样你就可以得到转义,多行等。支持,不必自己编写解析器。 :)
  • ...否则你会得到source "file" 的错误——通过运行在双引号内插入扩展的真实shell 解析器,将foo=bar baz 视为命令baz 的调用使用环境变量 foo 临时设置等 - 与您的 Python 代码所做的事情并不完全相同。好吧,无论如何,您都有这些错误,但更多没有shlex
  • ...也就是说:如果您真的想在 bash 中正确解析 INI 样式的文件,即使是没有 cmets 或节头的文件,它也比调用 source input.ini 更复杂。不一定更多——可能看起来像这样:while IFS="=" read -r var value; do printf -v "$var" '%s' "$value"; done <input.ini,如果外壳是现代 bash(printf -v 在基线 POSIX sh 中不可用),这就足够了。
【解决方案2】:

考虑类似于以下内容:

#!/bin/bash
#      ^^^^ NOT /bin/sh, which doesn't have process substitution available.

python_script='
import sys
d = {}                                    # create a context for variables
exec(open(sys.argv[1], "r").read()) in d  # execute the Python code in that context
for k in sys.argv[2:]:
  print "%s\0" % str(d[k]).split("\0")[0] # ...and extract your strings NUL-delimited
'

read_python_vars() {
  local python_file=$1; shift
  local varname
  for varname; do
    IFS= read -r -d '' "${varname#*:}"
  done < <(python -c "$python_script" "$python_file" "${@%%:*}")
}

然后您可以将其用作:

read_python_vars config.py week_date:dt cust_id:id
echo "Customer id is $id; date range is $dt"

...或者,如果您不想在读取变量时重命名它们,只需:

read_python_vars config.py week_date cust_id
echo "Customer id is $cust_id; date range is $week_date"

优点:

  • 不像简单的基于正则表达式的解决方案(它会在 Python 解析的某些细节上遇到问题 - 尝试教 sed 处理原始字符串和常规字符串,以及单引号和三引号,而不会将其变成毛球!) 或使用 Python 子进程的换行符分隔输出的类似方法,这将正确处理 str() 提供的任何对象,其中没有您的 shell 脚本可以使用的 NUL 字符。
  • 通过 Python 解释器运行内容还意味着您可以通过编程方式确定值 - 例如,您可以有一些 Python 代码向您的版本控制系统询问相关内容的最后更改日期。

    想想这样的场景:

    start_date = '01/03/16'
    end_date = '01/09/16'
    week_date = '%s-%s' % (start_date, end_date)
    

    ...使用 Python 解释器解析 Python 意味着您不会限制人们将来如何更新/修改您的 Python 配置文件。

现在,让我们谈谈注意事项:

  • 如果您的 Python 代码有副作用,这些副作用显然会生效(就像您选择 import 将文件作为 Python 中的模块一样)。不要使用它从您不信任其内容的文件中提取配置。
  • Python 字符串是 Pascal 风格的:它们可以包含文字 NUL。 shell 语言中的字符串是 C 风格的:它们以第一个 NUL 字符终止。因此,一些变量可以存在于 Python 中,而不是在没有非文字转义的情况下无法在 shell 中表示。为了防止 str() 表示包含 NUL 的对象溢出到其他分配中,此代码在字符串的第一个 NUL 处终止。

现在,让我们谈谈实现细节。

  • ${@%%:*}$@ 的扩展,它在每个参数中修剪并包括第一个 : 之后的所有内容,因此仅将 Python 变量名称传递给解释器。同样,${varname#*:} 是一个扩展,它修剪所有内容,包括传递给read 的变量名中的第一个:。见the bash-hackers page on parameter expansion
  • 使用&lt;(python ...) 是进程替换语法:&lt;(...) 表达式计算为一个文件名,读取时将提供该命令的输出。使用&lt; &lt;(...) 重定向该文件的输出,从而重定向该命令(第一个&lt; 是重定向,而第二个是启动进程替换的&lt;( 令牌的一部分)。使用这种形式将输出输出到while read 循环中可以避免BashFAQ #24 ("I set variables in a loop that's in a pipeline. Why do they disappear after the loop terminates? Or, why can't I pipe data to read?") 中提到的错误。
  • IFS= read -r -d '' 构造有一系列组件,每个组件都使read 的行为更符合原始内容:

    • 在命令执行期间清除 IFS 可防止从变量内容的末尾修剪空格。
    • 使用 -r 可以防止文字反斜杠被 read 本身使用,而不是在输出中显示。
    • 使用-d '' 将空字符串'' 的第一个字符设置为记录分隔符。由于 C 字符串是 NUL 终止的,并且 shell 使用 C 字符串,因此该字符是 NUL。这确保变量的内容可以包含任何非 NUL 值,包括文字换行符。

    有关在 bash 中从字符串读取面向记录的数据的过程的更多信息,请参阅 BashFAQ #001 ("How can I read a file (data stream, variable) line-by-line (and/or field-by-field)?")

【讨论】:

  • 如果我在shell脚本中保留变量名“dt”,在上面的代码之后会不会是dt=$week_date
  • 第二个&lt;后面应该有空格是&lt; &lt; (python -c "$python_script" filename.py week_date id)
  • @jxn,查看编辑;我在过程中明确了变量重命名,并尝试解决您关于&lt;() 语法的问题。
  • @jxn, ...btw,如果您收到有关该语法的错误,可能意味着您的代码使用 /bin/sh 而不是 @987654356 运行@.
  • 当我尝试重现解决方案@CharlesDuffy 时,我遇到了这个错误KeyError: 'week_date'
【解决方案3】:

我会做这样的事情。您可能需要稍微修改一下以包含/排除引号,因为我没有真正针对您的场景进行测试:

#!/bin/sh
exec <$python_filename
while read line
do
        match=`echo $line|grep "week_date ="`
        if [ $? -eq 0 ]; then
                dt=`echo $line|cut -d '"' -f 2`
        fi

        match=`echo $line|grep "cust_id ="`
        if [ $? -eq 0 ]; then
                cust_id=`echo $line|cut -d '"' -f 2`
        fi
done

【讨论】:

  • 需要更多报价。看看echo $line 如果line='hello * world' 做了什么。
  • ...当然,您会遇到天真的字符串解析方法带来的所有常见问题。如果是week_date = 'foo' 而不是week_date = "foo" 会怎样?还是week_date = r'foo'?还是week_date = get_current_datespan()
  • 另外,单独检查$? 是没有意义的;只需将您的 grep 放入您的 if 声明中:if printf '%s\n' "$line" | grep -q 'week_date ='; then
  • ...在那里使用printf '%s\n',因为它比echo更可靠;见pubs.opengroup.org/onlinepubs/009604599/utilities/echo.html——如果输入字符串包含任何反斜杠字符,你会看到echo的输出是标准未定义的;允许在没有任何-e-E 的情况下插入反斜杠转义序列;如果传递了-n,则允许它以未定义的方式运行(注意,undefined -- 抑制尾随换行符是不是标准定义的行为);而且一般来说很容易出错。
  • 另外,为输入文件中的每一行运行单独的grep 命令是非常效率低下的。即使您想坚持使用 POSIX 语法,我也建议使用 case 语句来进行模式匹配。即:case $line in "week_date ="*) ... ;; "cust_id ="*) ... ;; esac.
猜你喜欢
  • 1970-01-01
  • 2013-10-19
  • 1970-01-01
  • 1970-01-01
  • 2014-03-29
  • 1970-01-01
  • 2020-10-02
  • 1970-01-01
  • 2023-04-01
相关资源
最近更新 更多