20

I am listing the files in a directory and looping through them okay, BUT I need to know how many there are too. ${#dirlist[@]} is always 1, but for loop works?

#!/bin/bash
prefix="xxx"; # as example

len=${#prefix}; # string length
dirlist=`ls ${prefix}*.text`;
qty=${#dirlist[@]};  # sizeof array is always 1
for filelist in $dirlist
do
    substring="${filelist:$len:-5}";
    echo "${substring}/${qty}";
done

I have files xxx001.text upto xxx013.text
but all I get is 001/1 002/1 003/1

Waygood
  • 2,657
  • 2
  • 15
  • 16

5 Answers5

43

This:

dirlist=`ls ${prefix}*.text`

doesn't make an array. It only makes a string with space separated file names.

You have to do

dirlist=(`ls ${prefix}*.text`)

to make it an array.

Then $dirlist will reference only the first element, so you have to use

${dirlist[*]}

to reference all of them in the loop.

Rokin
  • 1,977
  • 2
  • 20
  • 32
KarelSk
  • 585
  • 4
  • 4
  • 14
    Use `"${dirlist[@]}"` instead of `${dirlist[*]}` to guard against files whose names contain whitespaces. – chepner Mar 05 '13 at 14:21
  • 6
    And please don't use `ls`! `dirlist=(${prefix}*.text)` is simpler and less error prone. – David Ongaro Nov 30 '16 at 22:40
  • @DavidOngaro Why is that? – codekandis May 07 '19 at 11:41
  • In the best case the `ls` is just redundant, since the shell is already doing the expansion of `${prefix}*.text` which `ls` will see as parameters. In the worst case `ls` will output additional information (like ANSI color codes, filtetype suffixes) depending on the environment, so the script will break. I guess this habit from using `ls` in front of a glob expression comes from a dos/windows mindset where `dir` was doing the expansion, but not the shell. – David Ongaro May 07 '19 at 19:08
  • 1
    And even putting performance aspects aside, it's just messier and unnecessary to create subshells and call external programs, if the shell can do the work. – David Ongaro May 07 '19 at 19:22
5

Declare an array of files:

arr=(~/myDir/*)

Iterate through the array using a counter:

for ((i=0; i < ${#arr[@]}; i++)); do

  # [do something to each element of array]

  echo "${arr[$i]}"
done
Mateen Ulhaq
  • 24,552
  • 19
  • 101
  • 135
dilshad
  • 734
  • 1
  • 10
  • 27
2

You're not creating an array unless you surround it with ( ):

dirlist=(`ls ${prefix}*.text`)
Costi Ciudatu
  • 37,042
  • 7
  • 56
  • 92
2
dir=/tmp
file_count=`ls -B "$dir" | wc -l`
echo File count: $file_count
Mikhail Vladimirov
  • 13,572
  • 1
  • 38
  • 40
0

The array syntax in bash is simple, using parentheses ( and ):

# string
var=name

# NOT array of 3 elements
# delimiter is space ' ' not ,
arr=(one,two,three) 
echo ${#arr[@]}
1

# with space
arr=(one two three)
# or ' ',
arr=(one, two, three)
echo ${#arr[@]}
3

# brace expansion works as well
# 10 elements
arr=({0..9})
echo ${#arr[@]}
10

# advanced one
curly_flags=(--{ftp,ssl,dns,http,email,fc,fmp,fr,fl,dc,domain,help});
echo ${curly_flags[@]}
--ftp --ssl --dns --http --email --fc --fmp --fr --fl --dc --domain --help
echo ${#curly_flags[@]}
12

if you want to run a command and store the output

# a string of output
arr=$(ls)
echo ${#arr[@]}
1

# wrapping with parentheses
arr=($(ls))
echo ${#arr[@]}
256

A more advanced / handy way is by using built-in bash commands mapfile or readarray and process substitution. here is is an example of using mapfile:

# read the output of ls, save it in the array name: my_arr
# -t    Remove a trailing DELIM from each line read (default newline)
mapfile -t my_arr < <(ls)
echo ${#my_arr[@]}
256
Shakiba Moshiri
  • 21,040
  • 2
  • 34
  • 44