Код отслеживания Google Analytics.

Feb 12, 2010

bash associative arrays(map, hash or how you them calls). part I

There are a lot of programming languages where you can use associative arrays. Some times it has different name - map or hash for example, but the fact is bash had nothing like this before version 4.0 And if you use bash with version lesser then 4.0 you probably try to find some kind of solution.



The main idea of the post is creating working code - the one step from theory to practice. After much researching in internet I've found two useful links about question:
last chapter in http://mywiki.wooledge.org/BashFAQ/006 and an article
How To Fake Associative Arrays In Bash. Actually there two main approaches - using variables and arrays. Lets look on both, but before we have to define the goal. I think it should be number of routines for manipulate associative arrays (or maps). Following list is probably minimum:
getMapValue <map> <key>
putMapValue <map> <key> <value>
getMapKeys <map>
removeMapKey <map> <key>
unsetMap <map>
isMap <map>
The first approach is to use indirect expansion and eval - every time when we will use map with name mapName and key someKey we can work with variable which name is "${mapName}${DELIMITER}${someKey}", where ${DELIMITER} can be anything (underscore for example). You can find full sources here
the callpart of code
getMapValue "${mapName}" "${someKey}"
...
variableName="$1${DELIMITER}$2"
printf "%s\n" "${!variableName}"
putMapValue "${mapName}" "${someKey}" "${someValue}"
...
variableName="$1${DELIMITER}$2"
eval "$variableName=\"$3\""
getMapKeys "${mapName}"
...
local keys=""
tmpStr='${!'$1${DELIMITER}'*}'
for variableName in $(eval "echo ${tmpStr}") ; do
    local key=${variableName/*${DELIMITER}/}
    if [[ -z "${keys}" ]]; then
        keys="$key"
    else
        keys+="$IFS$key"
    fi
done
printf "%s\n" ${keys}
removeMapKey "${mapName}" "${someKey}"
...
unset "$1${DELIMITER}$2"
unsetMap "${mapName}"
...
local mapName="$1"
for key in $(getMapKeys "${mapName}"); do
    removeMapKey "${mapName}" "${key}"
done
isMap "${mapName}"
...
local testStr=$(getMapKeys "${1}")
[[ -n "${testStr}" ]]

So it will work but let me quote BashFAQ which mentioned above:

Before you think of using eval to mimic this behavior in a shell (probably by creating a set of variable names like homedir_alex), try to think of a simpler approach that you could use instead. If this hack still seems to be the best thing to do, have a look at the following disadvantages:

1. It's hard to read and to maintain.

2. The variable names must match the RegularExpression ^[a-zA-Z_][a-zA-Z_0-9]* -- i.e., a variable name cannot contain arbitrary characters but only letters, digits, and underscores. We cannot have a variable's name contain Unix usernames, for instance -- consider a user named hong-hu. A dash '-' cannot be part of a variable name, so the entire attempt to make a variable named homedir_hong-hu is doomed from the start.

3. Quoting is hard to get right. If content strings (not variable name) can contain whitespace characters and quotes, it's hard to quote it right to preserve it through both shell parsings. And that's just for constants, known at the time you write the program.

4. If the program handles unsanitized user input, it can be VERY dangerous!

The second way will be described in part II

No comments: