Put Filenames in Bash Array

📅 April 8, 2017
Bash arrays can be tricky, so here are a few hints to aid understanding.

Quick Notes:

  • Create an Array: declare -a arrPics
  • Get Array Length: echo “${#arrPics[@]}”
  • Get All Elements as Separate Items: echo “${arrPics[@]}”
  • Get All Elements as a Single Line: echo “${arrPics[*]}”
  • Delete an Array: unset arrPics
  • Remove a Specific Element: unset arrPics[0]
  • List All Indices: echo “${!arrPics[@]}”
  • Get Length of Specific Element: echo “${@arrPics[0]}”

Create the Array

Suppose we have a directory of images, and we want to add the filename of each to the same Bash array.

#!/bin/bash

declare -a arrPics
for file in *.jpg
do
    arrPics=("${Pics[@]}" "$file")
done

 

declare -a arrPics

Declares an array. Empty by default, and indexed starting at 0. A Bash array is dynamic: It can grow and shrink as elements are added and removed, so you do not need to specify a fixed array size like other languages require.

We could also declare the array as

arrPics=()

and this would achieve the same result. declare with the -a option makes it clear that we are declaring an array. (The parentheses () might get confused with $() during a cursory glance.) Both work.

for file in *.jpg; do ... done

This gets the filenames of all files in the current directory that end with .jpg. In this case, this is what we want. Remember that Bash is case-sensitive, so files ending in .JPG  will be ignored.

arrPics=(${arrPics[@]} "$file")

This is the statement that performs the work of adding each new filename to the array. We are adding the array itself each time in order to preserve the existing array contents. If we did…

arrPics=("$file")

…then the array would only consist of one element — the last filename found.

How Many Elements in the Array?

echo ${#arrPics[@]}

will return the number of items in the array. Note the # character.

Show the Array

echo "${arrPics[@]}"

This will list the contents of the array on one line.

Sample Output:

P1240738.jpg P1240739.jpg P1240741.jpg P1240742.jpg P1240744.jpg P1240747.jpg P1240749.jpg P1240750.jpg P1240751.jpg P1240752.jpg P1240753.jpg P1240754.jpg P1240755.jpg P1240756.jpg P1240757.jpg P1240758.jpg

Another Listing:

echo "${arrPics[*]}"

This will also show the array elements in a single line.

P1240738.jpg P1240739.jpg P1240741.jpg P1240742.jpg P1240744.jpg P1240747.jpg P1240749.jpg P1240750.jpg P1240751.jpg P1240752.jpg P1240753.jpg P1240754.jpg P1240755.jpg P1240756.jpg P1240757.jpg P1240758.jpg

What is the Different Between [@] and [*] ?

This a tricky point to remember because the output above looks the same. However, the difference is significant.

[@] means: Output the elements as INDIVIDUAL items.

[*] means: Output the elements as ONE item.

Depending upon the Internal Field Separator (echo $IFS), which is usually set to a space or whitespace by default, array elements will be separated by the IFS character. You can change IFS to anything you want: IFS=”.” would separate by the dot character. The default is fine.

Here is a short script for demonstration:

#!/bin/bash

declare -a arrPics

for file in *.jpg
do
    arrPics=(${arrPics[*]} "$file")
done

# Using [@]
echo -e '\nUsing [@] -----------------------------------'
for item in "${arrPics[@]}"
do
    echo "ITEM: *** $item ***"
done

# Using [*]
echo -e '\nUsing [*] -----------------------------------'
for item in "${arrPics[*]}"
do
    echo "ITEM: *** $item ***"
done

 

We put a list of .jpg filenames into an array the same as before, but we show the contents using a for loop. Each loop iteration should put each element on its own line in the output. We use two loops: One with [@] and the second using [*].

Using [@] -----------------------------------
ITEM: *** P1240738.jpg ***
ITEM: *** P1240739.jpg ***
ITEM: *** P1240741.jpg ***
ITEM: *** P1240742.jpg ***
ITEM: *** P1240744.jpg ***
ITEM: *** P1240747.jpg ***
ITEM: *** P1240749.jpg ***
ITEM: *** P1240750.jpg ***
ITEM: *** P1240751.jpg ***
ITEM: *** P1240752.jpg ***
ITEM: *** P1240753.jpg ***
ITEM: *** P1240754.jpg ***
ITEM: *** P1240755.jpg ***
ITEM: *** P1240756.jpg ***
ITEM: *** P1240757.jpg ***
ITEM: *** P1240758.jpg ***

Using [*] -----------------------------------
ITEM: *** P1240738.jpg P1240739.jpg P1240741.jpg P1240742.jpg P1240744.jpg P1240747.jpg P1240749.jpg P1240750.jpg P1240751.jpg P1240752.jpg P1240753.jpg P1240754.jpg P1240755.jpg P1240756.jpg P1240757.jpg P1240758.jpg ***

Notice the difference?

When we use “${arrPics[@]}”, each element in the array is treated as a SEPARATE item.

When we use “${arrPics[*]}”, all array elements are treated as a SINGLE item.

What can cause confusion between [@] and [*] is that their outputs will appear identical without this contrived example.

echo "${arrPics[@]}"

P1240738.jpg P1240739.jpg P1240741.jpg P1240742.jpg P1240744.jpg P1240747.jpg P1240749.jpg P1240750.jpg P1240751.jpg P1240752.jpg P1240753.jpg P1240754.jpg P1240755.jpg P1240756.jpg P1240757.jpg P1240758.jpg

echo "${arrPics[*]}"

P1240738.jpg P1240739.jpg P1240741.jpg P1240742.jpg P1240744.jpg P1240747.jpg P1240749.jpg P1240750.jpg P1240751.jpg P1240752.jpg P1240753.jpg P1240754.jpg P1240755.jpg P1240756.jpg P1240757.jpg P1240758.jpg

The for loop test reveals the difference. Most likely, “${arrPics[@]}” is what we want. Note that double quotes are necessary to produce the result differences.

Deleting the Array

unset arrPics

That’s it. The array will be cleared and deleted. If you try to list its contents again, only blank lines will appear in the terminal.

Get Range of Elements

echo "${arrPics[@]:0:3}"

This gets the first three elements starting at index 0.

echo "${arrPics[@]:10:2}"

This gets two consecutive elements starting at index 10. If either the starting index or the limit is out of range or nonexistent, Bash will try to return what it can and ignore the rest.

Remove a Specific Element

unset arrPics[0]

Removes the element at index 0. The array “shifts back” to fill the vacancy, so removing an element will not leave a blank value or produce a hole in the array. For example, if arrPics[0] contains “red” and arrPics[1] contains “blue,” then arrPics[0] will contain “blue” after we run unset arrPics[0].

List All Indices

echo "${!arrPics[@]}"

This will list all indices of the array starting at 0. If we have an array of 16 elements, then the output will be:

0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15

Get Length of Specific Element

echo "${#arrPics[0]}"

This returns the length of the item at index 0. If the string ABCDEFG.jpg exists at index 0, then the output is 11.

Note that we use a specific index instead of [@] or [*] like we did earlier to get the number of items in the array.

Conclusion

Much more is possible with Bash arrays, so explore and have fun!

,

  1. Leave a comment

Leave a comment