2

I'm trying to build a basic REPL in bash. The script dynamically populates a list of files in a directory for the user to run.

File space:

|
|\ scripts/
|| script1.sh
|| script2.sh
|
\  shell/
 | shell.bashrc
 | shell.desktop

From shell.bashrc, I'm using the following command to get an array of filenames:

readarray -d " " filenames < <(find ../bin -type f -executable)

I can print the array filenames just fine and it contains a space separated string that holds "script1.sh script2.sh" as expected.

But when I try to access the first element of the array with echo ${filenames[0]} it prints every element. Any other index besides 0 returns an empty string.

My bash version is 5.0.17, and the first line of the file is #!/bin/bash

I moved to using "readarray" after trying the following led to similar results:

filenames=($(find "../bin" -type f -executable))

Edit: Found a dumb workaround and would still like to know where the original post is messing up. Workaround:

readarray -d " " filenames < <(find ../bin -type f -executable)
arr=($filenames)
echo ${arr[1]}

Which prints the 6th element of the array as expected.

muru
  • 72,889

1 Answers1

2

By default, find outputs results separated by newlines. By setting -d " " in the mapfile/readarray command, you are causing (assuming none of the names contains a space character) all of the results to be concatenated into a single string - newlines and all. When you then echo ${filenames[0]} (with unquoted variable expansion ${filenames[0]} and the default space-tab-newline value of IFS), the shell splits on newline, and echo reassembles the result using spaces1.

Instead use

readarray -t filenames < <(find ../bin -type f -executable)

which will parse the input as newline separated data, but strip the trailing newlines from the stored elements. Or - better - if your bash version supports it,

readarray -t -d '' filenames < <(find ../bin -type f -executable -print0)

which uses null bytes instead of newlines (making it safe for all legal filenames, even those that contain newlines).


1 See When is double-quoting necessary?

steeldriver
  • 81,074
  • 1
    Using variables without double-quotes (e.g. echo ${filenames[0]} instead of echo "${filenames[0]}") is almost always a mistake (see here and here). Especially for arrays, declare -p filename is often even more helpful at figuring out what's actually stored in a variable. – Gordon Davisson Jul 13 '21 at 19:23
  • Thank you! Both solutions worked like a charm. I'm glad that the solution was a bit more than a syntax error haha. The reason my workaround worked then is because in the reassembling of filenames[0], it was a space separated string as expected for the indexing, right? – user3116064 Jul 13 '21 at 19:28
  • @user3116064 almost - it's really because $filenames is equivalent to ${filenames[0]} and the (...) array constructor uses the same space-tab-newline IFS value, so it doesn't matter which particular whitespace character is the delimiter. – steeldriver Jul 13 '21 at 19:31