Wayne Dawson
Wayne Dawson

Reputation: 31

using "ls" and preserving the spaces in the resulting array

I am trying to read a directory with "ls" and do operations on it

directory example:

$ ls -1
x x
y y
z z

script file: myScript.sh

#!/bin/bash
files=(`ls -1`); 
for ((i=0; i<"${#files[@]}"; i+=1 )); do
    echo "${files[$i]}"
done

however, the output is

$ myScript.sh
x
x
y
y
z
z

yet if I define "files" in the following way

$ files=("x x" "y y" "z z")
$ for ((i=0; i<"${#files[@]}"; i+=1 )); do echo "${files[$i]}"; done
x x
y y
z z

How can I preserve the spaces in "files=(`ls -1`)"?

Upvotes: 0

Views: 2388

Answers (3)

Wayne Dawson
Wayne Dawson

Reputation: 31

It seems the main conclusion is not to use ls. Back in Pleistocene age of Unix programming, they used ls; however, these days, ls is best-restricted to producing human-readable displays only. A robust script for anything that can be thrown at your script (end lines, white spaces, Chinese characters mixed with Hebrew and French, or whatever), is best achieved by some form of globbing (as recommended by others here BashPitfalls).

#!/bin/bash
 for file in ./*; do
    [ -e "${file}" ] || continue
    # do some task, for example, test if it is a directory.
    if [ -d "${file}" ]; then
        echo "${file}"
    fi
done

The ./ is maybe not absolutely necessary, but it may help if the file begins with a "-", clarifying which file has the return line (or lines), and likely some other nasty buggers. This is also a useful template for specific files (.e.g, ./*.pdf). For example, suppose somehow the following files are in your directory: "-t" and "<CR>t". Then (revealing other issues with ls when using nonstandard characters)

$ ls
-t  ?t
$ for file in *; do ls "${file}"; done
-t  ?t
?t

whereas:

$ for file in ./*; do ls "${file}"; done
./-t
./?t

also

$ for file in ./*; do echo "${file}"; done
./-t
./
t

A workaround with POSIX commands can be achieved by --

$ for file in *; do ls -- "${file}"; done # work around
-t
?t

Upvotes: 1

Charles Duffy
Charles Duffy

Reputation: 295291

Don't.

See:


If at all possible, use a shell glob instead.

That is to say:

files=( * )

If you need to represent filenames as a stream of text, use NUL delimiters.

That is to say, either:

printf '%s\0' *

or

find . -mindepth 1 -maxdepth 1 -print0

will emit a NUL-delimited string, which you can load into a shell array safely using (in modern bash 4.x):

readarray -d '' array < <(find . -mindepth 1 -maxdepth 1 -print0)

...or, to support bash 3.x:

array=( )
while IFS= read -r -d '' name; do
  array+=( "$name" )
done < <(find . -mindepth 1 -maxdepth 1 -print0)

In either of the above, that find command potentially being on the other side of a FIFO, network stream, or other remoting layer (assuming that there's some complexity of that sort stopping you from using a native shell glob).

Upvotes: 5

Wiimm
Wiimm

Reputation: 3492

Try this:

eval files=($(ls -Q))

Option -Q enables quoting of filenames. Option -1 is implied (not needed), if the output is not a tty.

Upvotes: -4

Related Questions