使用 shell 脚本拆分字符串并提取变量

Split string and extract variables with shell script

问题

给出这个单行字符串:

PG_USER=postgres PG_PORT=1234 PG_PASS=icontain=and*symbols

将每个值分配给指定变量以便我以后可以使用它的正确方法是什么?


上下文

我正在 CronJob 中解析 k8s 秘密的上下文,以便我可以定期调用 Postgres 数据库中的存储过程。

为此,我计划使用:

PG_OUTPUT_VALUE=$(PGPASSWORD=$PG_PASSWD psql -qtAX -h $PG_HOST -p $PG_PORT -U $PG_USER -d $PG_DATABASE -c $PG_TR_CLEANUP_QUERY)

echo $PG_OUTPUT_VALUE

我目前正在尝试修复的实际整个舵图如下所示:

apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: {{ template "fullname" $ }}-tr-cleanup-cronjob
spec:
  concurrencyPolicy: Forbid
  schedule: "* * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: OnFailure
          volumes:
          - name: postgres
            secret:
              secretName: {{ template "fullname" $ }}-postgres
          containers:
          - name: {{ template "fullname" $ }}-tr-cleanup-pod
            image: postgres:12-alpine
            imagePullPolicy: Always
            env:
              - name: PG_PROPS
                valueFrom:
                  secretKeyRef:
                    name: {{ template "fullname" $ }}-postgres
                    key: postgres.properties
            command:
              - /bin/sh
              - -c
              - echo "props:" && echo $PG_PROPS && PG_USER=$(grep "^PG_USER=" | cut -d"=" -f2-) && echo $PG_USER && PG_TR_CLEANUP_QUERY="SELECT something FROM public.somewhere;" && echo $PG_TR_CLEANUP_QUERY && PG_OUTPUT_VALUE=$(PGPASSWORD=$PG_PASSWD psql -qtAX -h $PG_HOST -p $PG_PORT -U $PG_USER -d $PG_DATABASE -c $PG_TR_CLEANUP_QUERY) && echo PG_OUTPUT_VALUE
            volumeMounts:
              - name: postgres
                mountPath: /etc/secrets/postgres

当前方法

如您所见,我目前正在使用:

PG_USER=$(grep "^PG_USER=" | cut -d"=" -f2-)

那是因为我一开始以为secret会分多行输出,结果证明我错了。 echo $PG_USER 显示空字符串。

选项 1

可以重复使用此函数来单独分配每个变量:

extract() {
  echo "$INPUT" | grep -o "=.*" | cut -d" " -f1 | cut -d"=" -f2- ;
}

并使用它:

PG_USER=$(extract PG_USER)
PG_PORT=$(extract PG_PORT)
PG_PASS=$(extract PG_PASS)

选项 2

另一个可能的解决方案,存在安全问题,就是简单地使用:

eval "$INPUT"

只有在您验证输入后才应使用它。


上下文完整答案

因为我已经在问题中介绍了 k8s 上下文,所以这里是插入该解决方案的答案。

apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: {{ template "fullname" $ }}-cronjob
spec:
  concurrencyPolicy: Forbid
  schedule: "* * * * *"
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: OnFailure
          volumes:
          - name: postgres
            secret:
              secretName: {{ template "fullname" $ }}-postgres
          containers:
          - name: {{ template "fullname" $ }}-cronjob-pod
            image: postgres:12-alpine
            imagePullPolicy: Always
            env:
              - name: PG_PROPS
                valueFrom:
                  secretKeyRef:
                    name: {{ template "fullname" $ }}-postgres
                    key: postgres.properties
            command:
              - /bin/sh
              - -c
              - >-
                extract() { echo "$PG_PROPS" | grep -o "=.*" | cut -d" " -f1 | cut -d"=" -f2- ; } &&

                export PGHOST=$(extract PG_HOST) &&
                export PGPORT=$(extract PG_PORT) &&
                export PGDATABASE=$(extract PG_DATABASE) &&
                export PGUSER=$(extract PG_USER) &&

                PG_SCHEMA=$(extract PG_SCHEMA) &&
                PG_QUERY="SELECT tenant_schema FROM $PG_SCHEMA.tenant_schema_mappings;" &&

                PGPASSWORD=$(extract PG_PASSWD) psql --echo-all -c "$PG_QUERY"
            volumeMounts:
              - name: postgres
                mountPath: /etc/secrets/postgres

bashdeclare命令在这里很合适,比eval.

更安全

假设输入包含潜在的恶意内容

line='PG_USER=postgres PG_PORT=1234 PG_PASS=icontain=and*symbols`ls`'

我假设 none 个值包含空格。让我们拆分那个字符串

read -ra assignments <<< "$line"

现在,declare每一个

for assignment in "${assignments[@]}"; do declare "$assignment"; done

我们在检查输入的任何地方都维护双引号。

让我们看看我们最终得到了什么:

$ declare -p PG_USER PG_PORT PG_PASS
declare -- PG_USER="postgres"
declare -- PG_PORT="1234"
declare -- PG_PASS="icontain=and*symbols\`ls\`"