Don't parse `ls` output — use globs or `find` instead

Piping `ls` into `grep` breaks on filenames with spaces or newlines. SC2010 recommends globs, `find`, or shell parameter matching.

Problem

`ls | grep pattern` is fragile because `ls` output is meant for human consumption, not machine parsing: filenames containing newlines, spaces, or control characters break the line-oriented assumption that `grep` and subsequent commands rely on. Column formatting, color codes, and locale-dependent sorting can also interfere depending on how `ls` is invoked. Because the shell already has native, filename-safe ways to filter file lists — globs with extended pattern matching, or `find` with `-name`/`-regex` — there is essentially never a correct reason to scrape `ls` output in a script.

Bad

ls | grep '\.log
#39;

Good

# Bash glob, handles all filenames correctly
for f in *.log; do
  [ -e "$f" ] || continue
  echo "$f"
done

# Or, recursively:
find . -maxdepth 1 -name '*.log'

Explanation

Globbing and `find` operate directly on filesystem entries rather than a formatted text stream, so they cannot be confused by unusual characters in filenames. The `[ -e "$f" ]` guard handles the case where the glob matches nothing and expands to the literal pattern (unless `nullglob` is set).

Related

SC2045

SC2044

SC2012

When It Matters

ls | grep pattern filters human-formatted output, so it inherits every ls quirk: colour codes can defeat the match, names with newlines produce phantom entries, and the grep pattern is matched against whatever formatting ls chose. A glob or find does the same filtering against real filenames. The classic failure is a script that works interactively and then behaves differently in cron, where ls is not writing to a terminal and formats its output differently.

Second Example

Note

find can also filter on age, size, type, and permissions — conditions a grep over ls output cannot express at all.

Exceptions

When a human is reading the result at a prompt, ls | grep is perfectly serviceable. The warning is about scripts, where the output feeds another command.

Faq

Q

How do I match names case-insensitively?

A

Enable shopt -s nocaseglob for globs, or use find -iname.

Q

What if the glob matches nothing?

A

Without nullglob the pattern is passed through literally and the loop runs once with a bogus value; nullglob makes it iterate zero times.

Q

Can globs match hidden files?

A

Not by default. Enable shopt -s dotglob, or match them explicitly with .[!.]* patterns.