1

There are many similar questions, but I didn't understand any answer as solution of the following problem:

I want to create a bash function which holds two arguments, first is a variable name and second is a search string (for a file search).

As a result, I want the variable name holding an array of the found files.

I have a directory containing following files:

  • a.jpg
  • b.tif
  • c.jpg

The function is defined as:

search-files--fill-array()
{
    local variable_name="${1}"
    declare -a eval ${variable_name}

    local search_string="${2}"
    echo "${search_string}"

    local nullglob_setting=$(shopt -p nullglob)
    shopt -s nullglob
    found_files=(${search_string})
    eval "${nullglob_setting}"

    eval ${variable_name}=(${found_files[@]})
}

but the last line results an syntax error. It should assign the array to the variable which was presented as first argument to the function, so the function is called:

search-files--fill-array my_variable_a "*.jpg"

So the desired result should be that

echo "${my_variable_a[@]}"

shows me the two files "a.jpg" and "c.jpg" as an array, i.e.

echo "${#my_variable_a[@]}"

shall result "2".

If it wouldn't have to be an array, the solution would be to use following last line:

eval ${variable_name}=\${found_files}

Without any success I also tried to use a while loop to form the new array from the old one, but this doesn't work too.

eval ${variable_name}="\${found_files[@]}"

produces that my_variable_a holds a string but not an array.

Background

Perhaps my approach is completely wrong.

To make it clear: I want to have filled some variables each with an array which holds the filenames of files found by globbing (always the same way with different search “strings”). I expect only files, without any respect of subdirectories and I have to get sure, the results are always reliably sorted.)

And I want to avoid redundant code.

The shopt part is necessary to assure, if no file exists in the current directory, the array is filled with zero entries, while without nullglob, there would be exact one entry with the content of the search “string“.

Solution

As Uncle Billy suggested, I can use declare -n var here.

The working function:

search-files--fill-array()
{
    declare -n function=$1
    local shopt_setting=$BASHOPTS
    shopt -s nullglob
    function=($2)
    [[ :$shopt_setting: = *:nullglob:* ]] || shopt -u nullglob
}

His proposed handling of resetting the nullglob causes the function to not include any eval anymore. You can read e.g. this stackexchange post to get to know, why avoiding eval is worth considering.

2
  • I gave you another way to set nullglob only in the function; see updated answer. Though you should be asking a different question about it.
    – user313992
    Commented Feb 29, 2020 at 10:29
  • At first, I didn't see the advantage of your nullglob handling, but you brought me into line. Thank you very much! Commented Feb 29, 2020 at 11:05

1 Answer 1

2

Use variable references:

search-files--fill-array()
{
  typeset -n a=$1
  local s=$BASHOPTS
  shopt -s nullglob
  a=($2)
  [[ :$s: = *:nullglob:* ]] || shopt -u nullglob
}

prompt> touch foo.txt bar.txt
prompt> search-files--fill-array foo '*.txt'
prompt> typeset -p foo
declare -a foo=([0]="bar.txt" [1]="foo.txt")

help declare for more details (declare is a synonym of typeset).


declare -a eval ${variable_name}

This will declare two arrays local to the function, one named eval and the other named with the content of ${variable_name}. No matter what you assign the them, it will be lost after the function returns. Use declare -g to make them global.

eval ${variable_name}=(${found_files[@]}) => syntax error

Same as echo foo=(bar) => syntax error. ( is a meta-character, it cannot be used anywhere, but only at the beginning of a command (eg. after ;, &&, etc) or as part of the special syntax used when assigning to an array.

9
  • Thank you very much! I didn't understand declare -n before and reading help declare did help me less then reading your example code. So I rewrote my function using declare -n. I want to thank also for suggesting an alternative way of setting (and resetting) the nullglob option, but I really wonder, what is it's advantage over my solution, since it unsets nullglob afterwards instead of ressetting it to the status before? Commented Feb 29, 2020 at 10:37
  • "what is it's advantage over my solution?" less (i.e. no) evals and subshells, though you probably won't see any difference in practice.
    – user313992
    Commented Feb 29, 2020 at 10:41
  • At first, I didn't understand your nullglob handling correctly. Now I see the advantage of avoiding evil evals. Thanks alot! Commented Feb 29, 2020 at 11:07
  • One way to test the shopt and enable it if it is not already if ! shopt -q nullglob; then shopt -s nullglob'; fi Which applies to all the shopt option in bash, ... just my two cents.
    – Jetchisel
    Commented Mar 1, 2020 at 3:42
  • @Jetchisel but how you restore it to its previous value? I don't think that shopt -q nullglob; local wasset=$?; ...; [[ $wasset = 0 ]] || shopt -u nullglob is any better than what I have now.
    – user313992
    Commented Mar 1, 2020 at 7:24

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .