3

我更喜欢将我的 bash 脚本编程为尽可能程序化。我在尝试这样做时遇到的一个困难是在函数之间传递数组数据时,bash 不太支持这项任务。

例如,在 bash 中使用多个硬编码的引用值初始化一个数组是很简单的,每个值可能包含多个单词:

declare -a LINES=( "Hello there" "loyal user" )
echo "Line 0: '${LINES[0]}'"
echo "Line 1: '${LINES[1]}'"
# Line 0: 'Hello there'
# Line 1: 'Loyal user'

但是,用函数的输出替换这些硬编码的值似乎效果不佳:

getLines() {
    echo "\"Hello there\" \"loyal user\""
}

local LINE_STR=$( getLines )
declare -a LINES=( ${LINE_STR} )
echo "Line 0: '${LINES[0]}'"
echo "Line 1: '${LINES[1]}'"
# Line 0: '"Hello'
# Line 1: 'there"'

我已经尝试了几乎所有允许的 bash 语句排列来克服这个问题。似乎行之有效的一种方法是“评估”:

local LINE_STR=$( getLines )
eval declare -a LINES=( ${LINE_STR} )
echo "Line 0: '${LINES[0]}'"
echo "Line 1: '${LINES[1]}'"
# Line 0: 'Hello there'
# Line 1: 'loyal user'

但是,这种方法存在安全问题,如下所示:

emulateUnsafeInput() {
    echo "\"\`whoami\` just got haxxored\" \"Hahaha!\""
}

local LINE_STR=$( emulateUnsafeInput )
eval declare -a LINES=( "${LINE_STR}" )
echo "Line 0: '${LINES[0]}'"
echo "Line 1: '${LINES[1]}'"
# Line 0: 'root just got haxxored'
# Line 1: 'Hahaha!'

'read -a' 似乎是一种可能的解决方案,尽管这是一个有问题的解决方案,因为当数据通过管道传输到其中时,'read' 将在子 shell 中运行,从而有效地将其变量堆栈与调用脚本的堆栈分开。

我应该考虑哪些解决方案来减轻“评估”方法的安全问题?我已经包含了以下脚本,它演示了我尝试过的无数方法:

#!/bin/bash

getLines() {
    echo "\"Hello there\" \"loyal user\""
}

emulateUnsafeInput() {
    echo "\"\`whoami\` just got haxxored\" \"Hahaha!\""
}

execute() {
(
    echo Test 01
    declare -a LINES=( "Hello there" "loyal user" )
    echo "Line 0: '${LINES[0]}'"
    echo "Line 1: '${LINES[1]}'"
    # Line 0: 'Hello there'
    # Line 1: 'loyal user'
);(
    echo Test 02
    local LINE_STR=$( getLines )
    declare -a LINES=( ${LINE_STR} )
    echo "Line 0: '${LINES[0]}'"
    echo "Line 1: '${LINES[1]}'"
    # Line 0: '"Hello'
    # Line 1: 'there"'
);(
    echo Test 03
    local LINE_STR=$( getLines )
    declare -a LINES=( "${LINE_STR}" )
    echo "Line 0: '${LINES[0]}'"
    echo "Line 1: '${LINES[1]}'"
    # Line 0: '"Hello there" "loyal user"'
    # Line 1: ''
);(
    echo Test 04
    local LINE_STR=$( getLines )
    eval declare -a LINES=( ${LINE_STR} )
    echo "Line 0: '${LINES[0]}'"
    echo "Line 1: '${LINES[1]}'"
    # Line 0: 'Hello there'
    # Line 1: 'loyal user'
);(
    echo Test 05
    local LINE_STR=$( getLines )
    eval declare -a LINES=( "${LINE_STR}" )
    echo "Line 0: '${LINES[0]}'"
    echo "Line 1: '${LINES[1]}'"
    # Line 0: 'Hello there'
    # Line 1: 'loyal user'
);(
    echo Test 06
    local LINE_STR=$( getLines )
    declare -a LINES=( $( echo ${LINE_STR} ) )
    echo "Line 0: '${LINES[0]}'"
    echo "Line 1: '${LINES[1]}'"
    # Line 0: '"Hello'
    # Line 1: 'there"'
);(
    echo Test 07
    local LINE_STR=$( getLines )
    declare -a LINES=( $( echo "${LINE_STR}" ) )
    echo "Line 0: '${LINES[0]}'"
    echo "Line 1: '${LINES[1]}'"
    # Line 0: '"Hello'
    # Line 1: 'there"'
);(
    echo Test 08
    local LINE_STR=$( getLines )
    declare -a LINES=( $( eval echo ${LINE_STR} ) )
    echo "Line 0: '${LINES[0]}'"
    echo "Line 1: '${LINES[1]}'"
    # Line 0: 'Hello'
    # Line 1: 'there'
);(
    echo Test 09
    local LINE_STR=$( getLines )
    declare -a LINES=( $( eval echo "${LINE_STR}" ) )
    echo "Line 0: '${LINES[0]}'"
    echo "Line 1: '${LINES[1]}'"
    # Line 0: 'Hello'
    # Line 1: 'there'
);(
    echo Test 10
    local LINE_STR=$( emulateUnsafeInput )
    eval declare -a LINES=( ${LINE_STR} )
    echo "Line 0: '${LINES[0]}'"
    echo "Line 1: '${LINES[1]}'"
    # Line 0: 'root just got haxxored'
    # Line 1: 'Hahaha!'
);(
    echo Test 11
    local LINE_STR=$( emulateUnsafeInput )
    eval declare -a LINES=( "${LINE_STR}" )
    echo "Line 0: '${LINES[0]}'"
    echo "Line 1: '${LINES[1]}'"
    # Line 0: 'root just got haxxored'
    # Line 1: 'Hahaha!'
);(
    echo Test 12
    local LINE_STR=$( emulateUnsafeInput )
    declare -a LINES=( $( eval echo ${LINE_STR} ) )
    echo "Line 0: '${LINES[0]}'"
    echo "Line 1: '${LINES[1]}'"
    # Line 0: 'root'
    # Line 1: 'just'
);(
    echo Test 13
    local LINE_STR=$( emulateUnsafeInput )
    declare -a LINES=( $( eval echo "${LINE_STR}" ) )
    echo "Line 0: '${LINES[0]}'"
    echo "Line 1: '${LINES[1]}'"
    # Line 0: 'root'
    # Line 1: 'just'
)
}

execute
4

3 回答 3

2

以下正确处理数组元素中的空格

#! /bin/bash

# $ ./return_an_array.sh 
# ./return_an_array.sh: line 9: declare: returned_array: not found
# declare -a returned_array='([0]="one" [1]="two three")'

return_an_array()
{
    local -a an_array=( 'one' 'two three' )
    declare -p an_array
}

declare -p returned_array
eval $(return_an_array | sed -e 's/^\(declare -a \)[^=]*=/\1 returned_array=/')
declare -p returned_array
于 2009-10-04T16:00:56.137 回答
1

对于数据函数echo -e,使用换行符分隔数据:

getLines() { echo -e "\"Hello there\"\n\"loyal user\""; }

要读取数据,请使用进程替换和重定向:

i=0
while read -r
do
    arr[i++]=$REPLY
done < <(getLines)
# Line 0: '"Hello there"'
# Line 1: '"loyal user"'

不过,这会在字符串周围留下引号。

基于这里的技术。

于 2009-08-25T04:51:13.717 回答
1

如果您担心来自函数的任意恶意数据,您应该简单地使用\0(aka. NUL) 分隔符来期待和解析字符串。它是唯一不能成为任何变量一部分的字符,因此不会与实际数据发生冲突。

haxxorz() {
    printf '%s\0' whoami
    printf '%s\0' '\`whoami\`'
    printf '%s\0' "\`whoami\`"
    printf '%s\0' "\"\`whoami\` is haxxor-proof"'!'"\""
    printf '%s' 'Trying to poison the $REPLY variable with a missing separator'
}

index=0
while IFS= read -r -d '' || [ -n "$REPLY" ]
do
    array[index++]="$REPLY"
done < <(haxxorz)

for element in "${array[@]}"
do
    echo "$element"
done

echo "${REPLY:-REPLY is empty}"

结果:

whoami
\`whoami\`
`whoami`
"`whoami` is haxxor-proof!"
Trying to poison the $REPLY variable with a missing separator
REPLY is empty

当一个计划一起出现时,我喜欢它!

于 2011-11-28T15:04:48.477 回答