I prefer to program my bash scripts to be as procedural as possible. One difficulty I've encountered in trying to do so happens when passing array data between functions, a task that's not well supported in bash.
As an example, it's trivial to initial an array in bash with multiple hard-coded, quoted values, each of which may contain multiple words:
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'
However, replacing such hard-coded values with the output of a function seems to not work so well:
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"'
I've tried almost every permutation of allowed bash statements to overcome this problem. The one approach that seems to work well is 'eval':
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'
However, this approach is wrought with security concerns, as demonstrated here:
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' seems like a possible solution, although a problematic one because 'read' will operate in a sub-shell when data is piped into it, effectively separating its variable stack from the one of the calling script.
What solutions should I consider to mitigate the security concerns of the 'eval' approach? I've included the following script which demonstrates the myriad of approaches I've tried:
#!/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