89

I want to get a list of files and then read the results into an array where each array element corresponds to a file name. Is this possible?

2
  • 1
    Yes, it is possible. Maybe not advisable if the names might contain arbitrary characters (spaces and newlines in the names cause grief), but it is doable. Which bit of the manual did you have difficulty understanding? Commented Jun 11, 2012 at 13:53
  • How is the list being defined? bash has arrays, but depending on how the list is generated, different techniquest are better than others. In any case, post also your own attempts to solve the problem. Commented Oct 26, 2021 at 11:57

6 Answers 6

139

Don't use ls, it's not intended for this purpose. Use globbing.

shopt -s nullglob
array=(*)
array2=(file*)
array3=(dir/*)

The nullglob option causes the array to be empty if there are no matches.

4
  • Thanks, Is there anyway I can pipe the results of these? I tried something like arr(* | grep ".txt") but it does not like it.
    – dublintech
    Commented Jun 11, 2012 at 14:00
  • 11
    @dublintech: You don't need grep, just include the string in your glob: array=(*.txt) or array=(*foo*) Commented Jun 11, 2012 at 14:02
  • 1
    You can also append filenames to an array, output filenames and loop through them.
    – vhs
    Commented Aug 4, 2018 at 6:08
  • To my pleasant surprise, this also works with filenames that have whitespace or special characters in them! Tested with GNU bash, version 5.0.
    – famzah
    Commented Jan 2, 2023 at 10:04
33

Following will create an array arr with ls output in current directory:

arr=( $(ls) )

Though using output of ls is not safe at all.

Much better and safer than ls you can use echo *:

arr=( * )

echo ${#arr[@]} # will echo number of elements in array

echo "${arr[@]}" # will dump all elements of the array
5
  • 11
    ls is not necessary and should not be used for this purpose. Commented Jun 11, 2012 at 13:54
  • Agreed, I just wanted to tell how to create an array from some command output. However I edited my answer to stress that ls output should be avoided.
    – anubhava
    Commented Jun 11, 2012 at 13:56
  • 3
    When the array expansion is not quoted, all elements of the array are represented as a string, rather than individual elements of the array. Unquoted ${arr[*]} and ${arr[@]} are the same.
    – jordanm
    Commented Jun 11, 2012 at 16:19
  • 1
    Does anyone knows what is the max number of filenames / elements an array can hold?
    – dat789
    Commented Mar 31, 2016 at 9:16
  • 3
    anubhava, please remove your arr=( $(ls) ) line. Or if you want to leave it, please explicitly add a mention like don't do this, it's broken. Commented Apr 1, 2018 at 12:09
4

In bash you can create an array of filenames with pathname expansion (globbing) like so:

#!/bin/bash
SOURCE_DIR=path/to/source
files=(
   "$SOURCE_DIR"/*.tar.gz
   "$SOURCE_DIR"/*.tgz
   "$SOURCE_DIR"/**/*
)

The above will create an array called files and add to it N array elements, where each element in the array corresponds to an item in SOURCE_DIR ending in .tar.gz or .tgz, or any item in a subdirectory thereof with subdirectory recursion possible as Dennis points out in the comments.

You can then use printf to see the contents of the array including paths:

printf '%s\n' "${files[@]}" # i.e. path/to/source/filename.tar.gz

Or using parameter substitution to exclude the pathnames:

printf '%s\n' "${files[@]##*/}" # i.e. filename.tgz
6
  • 1
    You need to have shopt -s globstar in order to use recursive globbing (**). Commented Aug 4, 2018 at 11:46
  • Not for me. And the default Bash on MacOS is 3.2 which does not have globstar. What does counting the characters in some directory names with echo /usr/**/ | wc -c output for you? On my Mac, in Sierra with Bash 3.2 or in Bash 4.4 with globstar off, it outputs 122. If I run Bash 4.4 with globstar on, I get 277904. The latter is clearly recursive and the former is not. BTW, shopt is defined, but I presume you mean globstar (in Bash 3.2, shopt -p globstar gives an error, in Bash 4.4 it shows whether it's set or unset). Commented Aug 4, 2018 at 13:09
  • 1
    hash only works with external executables (try hash -t ls and help hash) and isn't going to show anything for shopt because it's a builtin or for globstar because it's an option rather than an executable. Try type -a shopt to show where shopt is coming from and shopt by itself to show the settings of all your options. Use echo "$BASH_VERSION" to show the version of your currently running shell (if it's Bash) and look at the output of ps -o tty,command to see if it's actually /bin/bash. Compare find /usr -type d | wc -l and echo /usr/**/ | tr -cd " " | wc -c ... Commented Aug 5, 2018 at 12:36
  • ... the counts should be fairly close if recursive globbing is working. My counts are in the neighborhood of 3800. Commented Aug 5, 2018 at 12:36
  • 1
    I'm ok with leaving it here. It's not specific to the OP's question, but it's a useful technique when outputting file names/paths. Commented Aug 5, 2018 at 17:40
2

Actually, ls isn't the way to go. Try this:

declare -a FILELIST
for f in *; do 
    #FILELIST[length_of_FILELIST + 1]=filename
    FILELIST[${#FILELIST[@]}+1]=$(echo "$f");
done

To get a filename from the array use:

echo ${FILELIST[x]}

To get n filenames from the array starting from x use:

echo ${FILELIST[@]:x:n}

For a great tutorial on bash arrays, see: http://www.thegeekstuff.com/2010/06/bash-array-tutorial/

2
  • 2
    You should iterate over the file glob OR create an array using a file glob as in my answer - not both!. When adding elements to an array, there's no reason to use the length of the array in a complex index expression (it won't work as expected if the array is sparse, for one thing). array+=(element). There's no reason to use $(echo "$f") just do the assignment directly. There's a missing closing curly brace on one of your echo statements. Commented Sep 30, 2013 at 14:17
  • 2
    are you aware that what you're doing is a broken way to just do FILELIST = ( * )? (or, rather FILELIST += ( * )). Why on earth do you use $(echo "$f") instead of just "$f"? Commented Apr 1, 2018 at 12:12
1

Try this,

path="" # could set to any absolute path
declare -a array=( "${path}"/* )

I'm assuming you'll pull out the unwanted stuff from the list later.

0

If you need a more specific file listing that cannot be returned through globbing, then you can use process substitution for the find command with a while loop delimited with null characters.

Example:

files=()
while IFS= read -r -d $'\0' f; do
    files+=("$f")
done < <(find . -type f -name '*.dat' -size +1G -print0)

Not the answer you're looking for? Browse other questions tagged or ask your own question.