A Linux system holds hundreds of thousands of files. The professionals who seem to "just know" where everything is are really fluent with three search tools: shell wildcards for matching filenames, find for locating files by any property, and grep for searching inside files. Master these and no log line or config setting can hide from you.
Before a command runs, the shell expands wildcard patterns into matching filenames:
* – any sequence of characters (including none)? – exactly one character[abc] – one character from the set; [0-9] for a rangels *.md # all Markdown files here
ls report-??.txt # report-01.txt, report-42.txt ...
ls img_[0-9]*.png # img_1.png, img_20240101.png ...
cp src/*.js backup/ # wildcards work with any commandNote that * does not match hidden files (those starting with a dot), and an unmatched pattern is passed through literally on most shells. Always preview a destructive wildcard with ls before giving it to rm.
find walks a directory tree and tests every entry against your criteria: find WHERE CONDITIONS.
find . -name "*.log" # by name, in the current tree
find /etc -name "*.conf" # search a specific tree
find . -iname "readme*" # case-insensitive name
find . -type d -name node_modules # directories only (-type f = files)
find /var/log -size +10M # bigger than 10 MB
find . -mtime -7 # modified within the last 7 daysQuote the pattern ("*.log") so the shell hands it to find instead of expanding it first.
find can execute a command on every match, which turns it into a bulk-operations engine:
find . -name "*.tmp" -delete # delete matches
find . -type f -name "*.sh" -exec chmod u+x {} \; # make scripts executable
find . -name "*.log" -exec grep -l "ERROR" {} \; # combine with grep{} is replaced by each filename and \; ends the command. Test with a harmless action like -print before using -delete.
grep prints every line matching a pattern—the tool you will use most in this lesson:
grep "ERROR" app.log # lines containing ERROR
grep -i "error" app.log # case-insensitive
grep -n "TODO" main.py # show line numbers
grep -r "database_url" ~/project # recursive through a directory
grep -rn --include="*.js" "apiKey" . # only in .js files, with line numbers
grep -v "DEBUG" app.log # invert: lines NOT containing DEBUG
grep -c "GET" access.log # count matching lines
grep -A2 -B2 "panic" app.log # 2 lines of context after/beforegrep patterns are regular expressions, a mini-language for describing text:
grep "^root" /etc/passwd # ^ anchors to line start
grep "sh$" /etc/passwd # $ anchors to line end
grep -E "40[34]" access.log # 403 or 404
grep -E "error|warn" app.log # either word (-E enables extended syntax)Even these four patterns—start, end, character set, alternation—cover most daily needs.
find.grep -r.find /etc -name "*.conf" 2>/dev/null | head
grep -n "PATH" ~/.bashrc
grep -rc "root" /etc/passwd /etc/group
ls /etc/*.d 2>/dev/null | headThe 2>/dev/null hides permission-denied noise—a preview of redirection, coming next lesson.
*, ?, [...]) match filenames and work with every command.find locates files by name, type, size or age, and can act on matches with -exec or -delete.grep -rn searches text recursively with line numbers; -i, -v, -c, -A/-B refine results.^ and $ plus alternation | give you 80% of regex power.Next lesson: Pipes, Redirection and Command Chaining — connect these tools into pipelines.