This document provides solutions to Lab 3 exercises focusing on grep, find, file type detection, and error handling.
Answer:
Every Linux program has three standard file descriptors:
| Descriptor | Name | Number | Purpose |
|---|---|---|---|
| stdin | Standard Input | 0 | Data input (keyboard, pipes) |
| stdout | Standard Output | 1 | Normal output (terminal, files) |
| stderr | Standard Error | 2 | Error messages |
Why three separate streams?
- Separate concerns: Normal output vs errors
- Independent redirection: Handle each differently
- Pipelines: Errors don't contaminate data flow
- Debugging: Easy to isolate problems
Example:
# Both outputs visible
ls existing_file non_existing_file
# Separate them
ls existing_file non_existing_file > output.txt 2> errors.txtAnswer:
> - Redirect stdout (overwrite)
command > file.txt
# Sends normal output to file, overwrites if exists>> - Redirect stdout (append)
command >> file.txt
# Appends normal output to file< - Redirect stdin
command < input.txt
# Reads input from file instead of keyboard2> - Redirect stderr (overwrite)
command 2> errors.txt
# Sends errors to file, overwrites if exists2>> - Redirect stderr (append)
command 2>> errors.log
# Appends errors to file2>&1 - Redirect stderr to stdout
command > file.txt 2>&1
# Both stdout and stderr go to file.txt
# Order matters: stdout redirect must come first!>&2 or 1>&2 - Redirect stdout to stderr
echo "Error message" >&2
# Useful in scripts to send messages to stderr&> - Redirect both stdout and stderr (overwrite)
command &> all_output.txt
# Modern syntax, both streams to file&>> - Redirect both stdout and stderr (append)
command &>> all_output.log
# Append both streams to file<< - Here document
cat << EOF
Multiple lines
of input
EOF<<< - Here string
grep "pattern" <<< "search this string"Examples:
# Separate output and errors
gcc program.c > output.txt 2> errors.txt
# Discard errors
find / -name "*.txt" 2> /dev/null
# Combine and pipe
command 2>&1 | grep "error"
# Save everything
./script.sh &> complete.logTask: Find all lines containing "jpg" in ~wojnicki/lab/Lab3.md, ignoring case.
Solution:
grep -i "jpg" ~wojnicki/lab/Lab3.mdExplanation:
grepsearches for patterns in files-imakes search case-insensitive- Matches: jpg, JPG, Jpg, jPg, etc.
- Displays entire lines containing the pattern
Output example:
Files ending in .JPG
Process all .jpg images
Alternative methods:
# Using extended regex
grep -iE "jpg" ~wojnicki/lab/Lab3.md
# Using cat and pipe
cat ~wojnicki/lab/Lab3.md | grep -i "jpg"Common mistakes:
- Forgetting
-i(misses JPG, Jpg, etc.) - Using wrong filename/path
Task: Show lines containing "jpg" with 2 lines of context before each match.
Solution:
grep -B 2 -i "jpg" ~wojnicki/lab/Lab3.mdExplanation:
-B 2shows 2 lines before each match-icase-insensitive (from previous task)- Helps understand context of matches
Output example:
This is line before before
This is line before
Files ending in .JPG
--
Another context line
Previous line
Process all .jpg images
Note: -- separates different match groups.
Alternative options:
# 2 lines after
grep -A 2 -i "jpg" file
# 2 lines before AND after
grep -C 2 -i "jpg" file
# or equivalently:
grep -2 -i "jpg" fileCommon mistakes:
- Using
-Ainstead of-B(shows after, not before) - Confusing number of lines
Task: Show lines containing "jpg" with 2 lines before AND line numbers.
Solution:
grep -B 2 -n -i "jpg" ~wojnicki/lab/Lab3.mdExplanation:
-B 2shows 2 lines before-ndisplays line numbers-icase-insensitive- Line numbers help locate matches in file
Output example:
23-This is line before before
24-This is line before
25:Files ending in .JPG
--
42-Another context line
43-Previous line
44:Process all .jpg images
Note:
- Context lines use
-after line number - Matching lines use
:after line number
Alternative:
# Just matches with line numbers
grep -n -i "jpg" ~wojnicki/lab/Lab3.mdCommon mistakes:
- Forgetting
-nflag - Misunderstanding
-vs:in output
Task: Count how many lines contain the letter "k" in the lab description.
Solution:
grep -c "k" ~wojnicki/lab/Lab3.mdOr case-insensitive (k or K):
grep -i -c "k" ~wojnicki/lab/Lab3.mdExplanation:
grepsearches for pattern-ccounts matching lines (not occurrences!)- Outputs a single number
Output example:
42
Alternative methods:
# Manual count with wc
grep "k" ~wojnicki/lab/Lab3.md | wc -l
# Case-insensitive
grep -i "k" ~wojnicki/lab/Lab3.md | wc -lImportant distinction:
# Counts LINES with "k"
grep -c "k" file
# Counts total OCCURRENCES of "k"
grep -o "k" file | wc -lCommon mistakes:
- Thinking
-ccounts occurrences (it counts lines!) - Not considering case sensitivity
Task: Count lines that DON'T contain letter "k".
Solution:
grep -v -c "k" ~wojnicki/lab/Lab3.mdExplanation:
-vinverts match (shows non-matching lines)-ccounts those lines- Opposite of previous exercise
Alternative methods:
# Using wc
grep -v "k" ~wojnicki/lab/Lab3.md | wc -l
# Total lines minus lines with k
total=$(wc -l < ~wojnicki/lab/Lab3.md)
with_k=$(grep -c "k" ~wojnicki/lab/Lab3.md)
echo $((total - with_k))Verification:
# Lines with k + lines without k = total lines
grep -c "k" file # e.g., 42
grep -v -c "k" file # e.g., 38
wc -l file # e.g., 80
# 42 + 38 = 80 ✓Common mistakes:
- Forgetting
-v(counts lines WITH k instead)
Task: Count how many times the letter "k" appears (not just how many lines).
Solution:
grep -o "k" ~wojnicki/lab/Lab3.md | wc -lOr case-insensitive:
grep -i -o "k" ~wojnicki/lab/Lab3.md | wc -lExplanation:
-oshows only matching parts (one per line)- Each "k" appears on its own line
wc -lcounts those lines = total occurrences
Visual example:
Input line: "kite makes me think"
With -o:
k
k
k
wc -l output: 3
Using tr (alternative approach):
cat ~wojnicki/lab/Lab3.md | tr -cd 'k' | wc -cExplanation of tr method:
tr -cd 'k'deletes all characters except 'k'wc -ccounts remaining characters (all 'k's)
For case-insensitive tr method:
cat ~wojnicki/lab/Lab3.md | tr -cd 'kK' | wc -cComparison:
# Task 4: Lines containing k
grep -c "k" file # e.g., 42 lines
# Task 6: Total k occurrences
grep -o "k" file | wc -l # e.g., 156 totalCommon mistakes:
- Using
-cwithout-o(counts lines, not occurrences) - Forgetting case sensitivity
Task: Display the entire lab description in a single line.
Solution:
cat ~wojnicki/lab/Lab3.md | tr -d '\n'Or:
tr -d '\n' < ~wojnicki/lab/Lab3.mdExplanation:
tr -d '\n'deletes all newline characters- All lines concatenate into one
Verification:
# Check it's really one line
cat ~wojnicki/lab/Lab3.md | tr -d '\n' | wc -l
# Output: 1To add newline at end:
cat ~wojnicki/lab/Lab3.md | tr -d '\n'; echoAlternative using sed:
sed ':a;N;$!ba;s/\n//g' ~wojnicki/lab/Lab3.mdAlternative using awk:
awk '{printf "%s",$0}' ~wojnicki/lab/Lab3.mdCommon mistakes:
- Using
tr '\n' ' '(replaces with spaces, still multiple words) - Not verifying with
wc -l
Task: Copy all files ending in .JPG from zasoby directory to ~/lab3/jpg using a single command.
Solution:
First, create target directory:
mkdir -p ~/lab3/jpgThen copy files:
cp ~wojnicki/lab/zasoby/*.JPG ~/lab3/jpg/Or using find:
find ~wojnicki/lab/zasoby -name "*.JPG" -exec cp {} ~/lab3/jpg/ \;Explanation:
*.JPGwildcard matches all files ending in .JPGcpcopies all matched files to destination- Shell expands wildcard before running cp
Verification:
ls ~/lab3/jpg/
# Should list all copied .JPG filesIf case doesn't matter:
# Using find for case-insensitive
find ~wojnicki/lab/zasoby -iname "*.jpg" -exec cp {} ~/lab3/jpg/ \;Common mistakes:
- Not creating destination directory first
- Wrong wildcard pattern
- Copying from wrong source directory
Task: Delete files from ~/lab3/jpg that aren't actually JPEG images (regardless of filename).
Solution:
cd ~/lab3/jpg
for file in *; do
if ! file "$file" | grep -q "JPEG\|JPG"; then
rm "$file"
fi
doneOr more concise:
cd ~/lab3/jpg && file * | grep -v "JPEG\|image data" | cut -d: -f1 | xargs rmExplanation:
file *examines all files, shows typesgrep -v "JPEG\|image data"filters out actual JPEGscut -d: -f1extracts filenamesxargs rmdeletes those files
Step-by-step breakdown:
Step 1 - Check file types:
file ~/lab3/jpg/*
# Output:
# file1.JPG: JPEG image data
# file2.JPG: ASCII text
# file3.JPG: JPEG image dataStep 2 - Find non-JPEGs:
file ~/lab3/jpg/* | grep -v "JPEG"
# Output:
# file2.JPG: ASCII textStep 3 - Extract filenames and delete:
file ~/lab3/jpg/* | grep -v "JPEG" | cut -d: -f1 | xargs rmSafe version (shows what would be deleted):
cd ~/lab3/jpg
for file in *; do
if ! file "$file" | grep -q "JPEG"; then
echo "Would delete: $file ($(file -b $file))"
# rm "$file" # Uncomment to actually delete
fi
doneCommon mistakes:
- Trusting file extensions (task specifically says ignore names)
- Not testing before deleting
- Wrong grep pattern
Task: Find all directories recursively in /home without showing error messages.
Solution:
find /home -type d 2> /dev/nullExplanation:
find /homesearches /home directory-type dmatches only directories2> /dev/nullredirects errors to null device (discards them)
Why errors occur:
- Permission denied on some directories
- Normal users can't read all of /home
Alternative (redirect to file for review):
find /home -type d 2> ~/lab3/find_errors.txtVerification:
# Count directories found
find /home -type d 2> /dev/null | wc -lCommon mistakes:
- Not redirecting stderr (screen filled with errors)
- Using
> /dev/nullinstead of2>(hides results, not errors!) - Wrong redirection:
2>&1 > /dev/null(incorrect order)
Task: Display directories from previous exercise on screen AND save to ~/lab3/home.
Solution:
find /home -type d 2> /dev/null | tee ~/lab3/homeExplanation:
findsearches for directories2> /dev/nullhides errors| tee ~/lab3/homesplits output: shows on screen + saves to file
How tee works:
find → tee → screen (stdout)
↓
~/lab3/home
To append instead of overwrite:
find /home -type d 2> /dev/null | tee -a ~/lab3/homeVerification:
# Check file was created and has content
wc -l ~/lab3/home
cat ~/lab3/home | headAlternative (save both output and errors):
find /home -type d 2>&1 | tee ~/lab3/home_with_errorsCommon mistakes:
- Using
>instead of| tee(only saves, doesn't display) - Not handling errors properly
Task: Find directories containing digit "0" in their names within /home, hide errors.
Solution:
find /home -type d -name "*0*" 2> /dev/nullExplanation:
find /homesearches /home-type donly directories-name "*0*"name contains "0" anywhere2> /dev/nullhides errors
Pattern matching:
*0*matches: home0, backup10, 2024, etc.*matches any characters before/after "0"
Examples of matches:
/home/user0
/home/user/folder10
/home/student/2024
/home/backup/v0.1
Case-insensitive (if needed):
find /home -type d -iname "*0*" 2> /dev/nullVerification:
# Count matching directories
find /home -type d -name "*0*" 2> /dev/null | wc -lCommon mistakes:
- Wrong wildcard:
0*only matches names starting with 0 - Not specifying
-type d(includes files too)
Task: Repeat previous but limit to 3 levels deep.
Solution:
find /home -maxdepth 3 -type d -name "*0*" 2> /dev/nullExplanation:
-maxdepth 3stops recursion after 3 levels- Faster and more focused results
Level counting:
/home ← level 1
/home/user ← level 2
/home/user/dir ← level 3
/home/user/dir/sub ← level 4 (not searched)
Different depth limits:
# Only /home itself
find /home -maxdepth 1 -type d -name "*0*" 2> /dev/null
# Up to 2 levels
find /home -maxdepth 2 -type d -name "*0*" 2> /dev/null
# Start at level 2 (skip /home direct children)
find /home -mindepth 2 -maxdepth 3 -type d -name "*0*" 2> /dev/nullComparison:
# Without limit (might be thousands)
find /home -type d -name "*0*" 2> /dev/null | wc -l
# With limit (much fewer)
find /home -maxdepth 3 -type d -name "*0*" 2> /dev/null | wc -lCommon mistakes:
- Confusing maxdepth with number of subdirectories
- Not understanding level counting
Task: Find all text files in ~/lab1 and copy them to ~/kopia_zapasowa.
Solution:
First, create backup directory:
mkdir -p ~/kopia_zapasowaThen find and copy:
find ~/lab1 -type f -name "*.txt" -exec cp {} ~/kopia_zapasowa/ \;Explanation:
find ~/lab1searches lab1 directory-type fonly regular files (not directories)-name "*.txt"files ending in .txt-exec cp {} ~/kopia_zapasowa/ \;copies each file{}placeholder for found filename\;terminates the -exec command
Alternative using xargs:
find ~/lab1 -type f -name "*.txt" | xargs -I {} cp {} ~/kopia_zapasowa/For all text files (not just .txt extension):
find ~/lab1 -type f -exec file {} \; | grep "text" | cut -d: -f1 | xargs -I {} cp {} ~/kopia_zapasowa/Verification:
# List copied files
ls ~/kopia_zapasowa/
# Count files
find ~/lab1 -name "*.txt" | wc -l
ls ~/kopia_zapasowa/*.txt | wc -l
# Should matchCommon mistakes:
- Forgetting
\;at end of -exec - Wrong syntax:
cp ~/kopia_zapasowa/ {}(reversed arguments) - Not creating destination directory first
Task: Find all files in ~wojnicki/lab with "lab" in name (ignore case).
Solution:
find ~wojnicki/lab -iname "*lab*"Explanation:
find ~wojnicki/labsearches the directory-inamecase-insensitive name matching"*lab*"matches: lab, Lab, LAB, LabFile, mylab, etc.
Examples of matches:
Lab1.md
laboratory.txt
LABFILE
research_lab_data
Alternative (using grep after find):
find ~wojnicki/lab -type f | grep -i "lab"Only files (not directories):
find ~wojnicki/lab -type f -iname "*lab*"Verification:
# Count matches
find ~wojnicki/lab -iname "*lab*" | wc -lCommon mistakes:
- Using
-nameinstead of-iname(misses case variants) - Forgetting wildcards:
-iname "lab"(exact match only)
find ~wojnicki -type f -name "*.txt" -exec cp {} ~/kopia_zapasowa/ \; 2>&1 | tee -a plik.txtComplete Explanation:
Part 1: find ~wojnicki -type f -name "*.txt"
find ~wojnicki- Search in wojnicki's home directory-type f- Find only regular files (not directories)-name "*.txt"- Match files ending in .txt
Part 2: -exec cp {} ~/kopia_zapasowa/ \;
-exec- Execute command on each found filecp- Copy command{}- Placeholder replaced with found filename~/kopia_zapasowa/- Destination directory\;- Terminates the -exec command
Part 3: 2>&1
2>&1- Redirect stderr (errors) to stdout- Combines error messages with normal output
- Allows both to be piped together
Part 4: | tee -a plik.txt
|- Pipe combined outputtee- Duplicate stream-a- Append mode (don't overwrite)plik.txt- Log file
What it does step by step:
- Find all .txt files in ~wojnicki
- Copy each to ~/kopia_zapasowa/
- Combine stdout and stderr
- Display on screen AND append to plik.txt
Example output in plik.txt:
Copied: /home/wojnicki/file1.txt
Copied: /home/wojnicki/docs/file2.txt
cp: cannot stat '/home/wojnicki/protected/file3.txt': Permission denied
Use cases:
- Backup files with logging
- Monitor copy operation in real-time
- Preserve record of errors for review
Alternative without tee (just logging):
find ~wojnicki -type f -name "*.txt" -exec cp {} ~/kopia_zapasowa/ \; &>> plik.txtgrep -i pattern file # Case-insensitive
grep -v pattern file # Invert (non-matching)
grep -n pattern file # Line numbers
grep -B 2 pattern file # 2 lines before
grep -A 2 pattern file # 2 lines after
grep -C 2 pattern file # 2 lines context
grep -c pattern file # Count lines
grep -o pattern file # Only matchesfind /path -name "*.txt" # By name
find /path -iname "*.txt" # Case-insensitive
find /path -type f # Files only
find /path -type d # Directories only
find /path -maxdepth 3 # Limit depth
find /path -name "*.txt" -exec cmd {} \; # Execute command
find /path -name "*.txt" 2>/dev/null # Hide errorscommand 2> errors.txt # Save errors
command 2> /dev/null # Discard errors
command &> all.txt # Save everything
command 2>&1 | tee log.txt # Display and logEnd of Model Answers for Lab 3