Bash 安全地从文件中获取变量

Bash Getting variables from a file safely

我有一个脚本可以将变量存储在 .txt 文件中供以后使用。我想从文件中安全地检索这些变量。

我现在是如何设置的:

Settings.txt

var1=value1
var2=value2
...

脚本

for i in $(cat Settings.txt); do $i done
# So now "var1" has the value "value1", and so on

这可行,但很危险,因为有人可以通过该 txt 文件注入代码。

我知道 source 命令,但它也有同样的问题。那么,如何安全的实现同样的功能呢?

您可以在采购前检查变量赋值格式:

#!/bin/bash

file=Settings.txt
regex_varname='^[a-zA-Z0-9_]\+\'
regex_varvalue='[a-zA-Z0-9]\+$'

is_safe_var() {
  while read var; do
    grep -q $regex_varname=$regex_varvalue <<< "$var" || return 1
  done < "$file"
}

is_safe_var && source "$file" || echo "Break"

变量$regex_varname$regex_varvalue赋值模式为变量授权名称和值:

  • ^[a-zA-Z0-9_] :以一个或多个字母数字字符或 _
  • 开头的变量名称
  • [a-zA-Z0-9]\+$ : 以一个或多个字母数字字符结尾的变量值

函数 is_safe_var 中的循环检查 Settings.txt 的每一行变量赋值是否匹配模式 $regex_varname=$regex_varvalue.

如果一行未通过测试,它 returns 来自带有错误代码和 echo "Break" 的函数,否则 Settings.txt 是来源。

注意:您可以在变量名称和值中使用授权字符来完成字符范围 [a-zA-Z0-9_][a-zA-Z0-9]

如果您不想单独执行验证和变量创建步骤:

更新,基于declare:一种仍然安全的更简单的方法是使用declare内置函数来定义变量:

#!/usr/bin/env bash

# Read the file line by line.
while read -r line; do
  declare -- "$line"
done <<'EOF'
  var1=value1
  var2=value2
EOF
  • declare 命令因输入行无效 shell 变量赋值而失败,但安全地 失败该行永远不会被评估为 command.

  • 请注意,这些值被读取为 文字 ,与文件中定义的完全相同(删除尾随空格除外)。

  • 如果您还想支持单引号或双引号值,请改用以下 declare 命令:
    declare -- "$(xargs -I {} printf %s {} <<<"$line")"
    但请注意,支持在值中使用嵌入、转义引号(这是xargs的限制)。


原始答案,基于printf -v:

#!/usr/bin/env bash

# Read the file line by line.
while read -r line; do
  # Split the line into name and value using regex matching.
  if [[ $line =~ ^([^=]+)=(.*)$ ]]; then
    # ${BASH_REMATCH[1]} now contains the variable name, 
    # ${BASH_REMATCH[2]} the value.
    # Use printf -v to store the value in a variable.
    printf -v "${BASH_REMATCH[1]}" %s "${BASH_REMATCH[2]}"
  fi
done <<'EOF'
  var1=value1
  var2=value2
EOF

# Print the variables that were created (based on name prefix `var`).
for name in ${!var*}; do
  printf '%s=%s\n' "$name" "${!name}"
done
  • 请注意,这些值被读取为 文字 ,与文件中定义的完全相同(删除尾随空格除外)。

    • 如果有单引号或双引号的值,并且您想删除引号,请改用以下 printf -v 命令:
      printf -v "${BASH_REMATCH[1]}" %s "$(xargs -I {} printf %s {} <<<"${BASH_REMATCH[2]}")"
      但请注意,嵌入、转义引号的引用字符串受支持。
  • 应该可以安全使用,因为 printf -v 用于创建变量 - shell 不直接作为赋值语句的来源,这是可能发生注入的地方.

  • 简单地跳过未识别为变量赋值的行。

  • 正则表达式 ^([^=]+)=(.*)$ 匹配任何以 (^) 开头的至少 1 (+) 个字符而不是 = ([^=]),紧接着是 =,接着是任何剩余的字符序列 (.*),直到行尾 ($)。 ([^=]+)(.*) 两边的括号确保捕获的子字符串保存在特殊的 Bash 数组变量 ${BASH_REMATCH[@]} 中,从索引 1 开始。

    • 为简单起见,没有尝试预先验证变量名称,这意味着 printf -v 命令稍后可能会失败。