Understanding the differences between BSD and GNU implementations of common Unix tools is essential for writing portable scripts and troubleshooting compatibility issues. This reference compares key tools and their variations.
🎯 Core Philosophy Differences
Design Philosophy
GNU Tools (Linux/Free Software Foundation)
- Feature-rich with extensive options
- Consistent interface across tools
- Extensive documentation and help systems
- GNU Coding Standards compliance
- GPL licensing
BSD Tools (Berkeley Software Distribution)
- Minimalist and focused design
- Simple, predictable behavior
- Conservative feature development
- Clear, concise man pages
- BSD licensing
Command-Line Interface
| # GNU tools often have long options
ls --human-readable --sort=size
grep --recursive --ignore-case --line-number "pattern" /path/
# BSD tools prefer short options
ls -lhS
grep -rin "pattern" /path/
|
ls - List Directory Contents
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 | # GNU ls (Linux)
ls -la --human-readable --color=auto --group-directories-first
# BSD ls (macOS, FreeBSD)
ls -laG # -G for color on BSD, -g for color on GNU
# Portable approach
if ls --version >/dev/null 2>&1; then
# GNU version
LS_COLOR="--color=auto"
else
# BSD version
LS_COLOR="-G"
fi
ls -la $LS_COLOR
|
Key Differences:
- Color support: GNU uses --color, BSD uses -G
- Human readable: Both support -h
- Sorting: GNU has more sorting options (--sort)
- Time format: Different default time displays
grep - Pattern Searching
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | # GNU grep
grep -r --exclude-dir=.git --include="*.sh" "pattern" .
# BSD grep (macOS)
grep -r --exclude-dir=.git --include="*.sh" "pattern" .
# However, BSD lacks some GNU extensions
# GNU only:
grep -P "perl_regex" file # Perl-compatible regex
grep --exclude="*.tmp" file # More exclude patterns
# Portable approach
if grep --help 2>&1 | grep -q "perl-regexp"; then
# GNU grep
GREP_EXTENDED="-P"
else
# BSD grep
GREP_EXTENDED="-E"
fi
|
Key Differences:
- Regex flavors: GNU supports -P (Perl), BSD doesn't
- Exclude patterns: GNU has more flexible exclusion options
- Context lines: Both support -A, -B, -C
- Performance: BSD grep often faster for simple searches
find - File Search
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 | # GNU find
find . -name "*.txt" -mtime -7 -exec ls -l {} \;
# BSD find
find . -name "*.txt" -mtime -7 -exec ls -l {} \;
# GNU-specific features not in BSD:
find . -regextype posix-extended -regex ".*\.txt$" # GNU only
find . -daystart -mtime -7 # GNU only
# Portable approach for common operations
find_files_by_extension() {
local ext="$1"
local days="${2:-7}"
if find --help 2>&1 | grep -q "regextype"; then
# GNU find
find . -name "*.${ext}" -mtime -${days} -type f
else
# BSD find
find . -name "*.${ext}" -mtime -${days} -type f
fi
}
|
Key Differences:
- Regex support: GNU has -regex with multiple types, BSD more limited
- Time specifications: GNU has -daystart, more granular options
- Operators: Both support standard boolean operators
- Performance: BSD find sometimes more efficient
sed - Stream Editor
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 | # GNU sed
sed -i 's/old/new/g' file.txt
# BSD sed
sed -i '' 's/old/new/g' file.txt # Requires empty string for backup
# Portable approach
if sed --version >/dev/null 2>&1; then
# GNU sed
sed -i 's/old/new/g' file.txt
else
# BSD sed
sed -i '' 's/old/new/g' file.txt
fi
# Extended regex
# GNU
sed -r 's/(.)(.)/\2\1/g' file
# BSD
sed -E 's/(.)(.)/\2\1/g' file
# Portable
if sed --help 2>&1 | grep -q "\\+"; then
SED_EXTENDED="-r" # GNU
else
SED_EXTENDED="-E" # BSD
fi
sed $SED_EXTENDED 's/(.)(.)/\2\1/g' file
|
Key Differences:
- In-place editing: BSD requires backup suffix (even if empty)
- Extended regex: GNU uses -r, BSD uses -E
- Escape sequences: Some differences in \n, \t handling
- Performance: BSD sed often faster for simple substitutions
awk - Pattern Scanning
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 | # Both generally compatible for basic usage
awk '{print $1}' file.txt
# GNU awk extensions
gawk 'BEGIN {
FPAT = "([^,]*)|(\"[^\"]+\")" # Handle quoted CSV fields
}'
# Portable approach
if command -v gawk >/dev/null 2>&1; then
AWK_CMD="gawk"
elif command -v awk >/dev/null 2>&1; then
AWK_CMD="awk"
else
echo "No awk found" >&2
exit 1
fi
# Use POSIX features for portability
$AWK_CMD 'BEGIN { FS=","; OFS="|" } { print $1, $2 }' file.csv
|
Key Differences:
- Extensions: GNU awk (gawk) has many extensions
- Built-in functions: Some math/string functions differ
- Performance: BSD awk often lighter weight
- Compatibility: Basic AWK is highly portable
tar - Tape Archive
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 | # GNU tar
tar --create --verbose --file=archive.tar.gz --gzip dir/
# BSD tar
tar -czvf archive.tar.gz dir/
# Portable approach
create_portable_archive() {
local archive="$1"
local source="$2"
if tar --version | grep -q "GNU"; then
# GNU tar
tar --create --gzip --verbose --file="$archive" "$source"
else
# BSD tar
tar -czvf "$archive" "$source"
fi
}
# Feature differences:
# GNU: --exclude, --exclude-vcs, --transform
# BSD: -s substitute pattern, -C change directory
|
Key Differences:
- Compression: Both support -z (gzip), -j (bzip2)
- Exclusion: GNU has more flexible exclude options
- Transformations: GNU --transform, BSD -s
- Long options: GNU supports --long-option format
ps - Process Status
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 | # GNU ps (Linux)
ps aux --forest # Tree view
ps -eo pid,ppid,cmd,%cpu,%mem --sort=-%cpu
# BSD ps (macOS, FreeBSD)
ps aux -O ppid # Custom format
ps -eo pid,ppid,command -r # Sort by CPU
# Portable approach for common needs
get_process_list() {
if ps --version >/dev/null 2>&1; then
# GNU ps
ps aux --no-headers
else
# BSD ps
ps aux
fi
}
|
Key Differences:
- Output format: Different default columns and ordering
- Sorting: GNU --sort, BSD -r, -m flags
- Custom fields: Different syntax for selecting fields
- Tree display: GNU --forest, BSD requires separate tools
date - Date and Time
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32 | # GNU date
date --iso-8601=seconds
date -d "tomorrow" +%s
date -d "@1234567890" # Convert timestamp
# BSD date
date -Iseconds
date -v+1d +%s # Tomorrow
date -r 1234567890 # Convert timestamp
# Portable approach
format_iso_date() {
if date --help >/dev/null 2>&1; then
# GNU date
date --iso-8601=seconds
else
# BSD date
date -Iseconds
fi
}
convert_timestamp() {
local timestamp="$1"
if date --help >/dev/null 2>&1; then
# GNU date
date -d "@$timestamp"
else
# BSD date
date -r "$timestamp"
fi
}
|
Key Differences:
- Date parsing: GNU -d, BSD -v, -r for different operations
- Format specifiers: Minor differences in % codes
- ISO formats: GNU --iso-8601, BSD -I
- Relative dates: Different syntax for time calculations
🛠️ Detection and Compatibility
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73 | # Detect GNU vs BSD tools
detect_tool_variant() {
local tool="$1"
case "$tool" in
ls)
if ls --version >/dev/null 2>&1; then
echo "gnu"
else
echo "bsd"
fi
;;
sed)
if sed --version >/dev/null 2>&1; then
echo "gnu"
else
echo "bsd"
fi
;;
grep)
if grep --version >/dev/null 2>&1; then
echo "gnu"
else
echo "bsd"
fi
;;
esac
}
# Universal tool wrapper
universal_tool() {
local tool="$1"
shift
case "$tool" in
sed)
if sed --version >/dev/null 2>&1; then
# GNU sed
sed "$@"
else
# BSD sed - handle in-place editing
local args=()
local inplace=false
local backup=""
while [[ $# -gt 0 ]]; do
case "$1" in
-i)
inplace=true
backup=""
shift
;;
-i*)
inplace=true
backup="${1#-i}"
shift
;;
*)
args+=("$1")
shift
;;
esac
done
if [ "$inplace" = true ]; then
sed -i "$backup" "${args[@]}"
else
sed "${args[@]}"
fi
fi
;;
esac
}
|
Feature Testing
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 | # Test for specific features
test_gnu_features() {
local features=()
# Test GNU-specific options
if ls --human-readable / >/dev/null 2>&1; then
features+=("ls-human-readable")
fi
if grep -P ".*" /dev/null >/dev/null 2>&1; then
features+=("grep-perl-regex")
fi
if find . -regextype posix-extended -regex ".*" >/dev/null 2>&1; then
features+=("find-regex")
fi
echo "${features[@]}"
}
# Adaptive scripting
ADAPTIVE_FEATURES=($(test_gnu_features))
use_human_readable_ls() {
if [[ " ${ADAPTIVE_FEATURES[*]} " == *" ls-human-readable "* ]]; then
ls -lh "$@"
else
ls -l "$@"
fi
}
|
🧾 Summary Comparison Chart
| Tool |
GNU Strengths |
BSD Strengths |
Portability Tips |
| ls |
Many options, colors |
Fast, clean output |
Check for --version |
| grep |
Perl regex, many options |
Fast, reliable |
Use -E not -r |
| find |
Powerful regex, excludes |
Consistent behavior |
Avoid GNU-specific flags |
| sed |
Long options, features |
Lightweight, fast |
Handle -i differently |
| tar |
Transform, exclude vcs |
Native BSD format |
Use common compression flags |
| date |
Flexible parsing |
Simple, reliable |
Different timestamp handling |
| ps |
Extensive formatting |
Standard output |
Use common field names |
Universal Script Template
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61 | #!/bin/bash
# Universal script that works on both GNU and BSD systems
# Detect system type
detect_system() {
if [ "$(uname)" = "Linux" ]; then
echo "linux"
elif [ "$(uname)" = "Darwin" ]; then
echo "darwin"
else
echo "other"
fi
}
SYSTEM_TYPE=$(detect_system)
# Tool compatibility functions
safe_sed() {
if sed --version >/dev/null 2>&1; then
# GNU sed
sed "$@"
else
# BSD sed
local args=()
while [[ $# -gt 0 ]]; do
case "$1" in
-i) args+=("-i" ""); shift ;; # Add empty backup
*) args+=("$1"); shift ;;
esac
done
sed "${args[@]}"
fi
}
safe_date() {
if date --help >/dev/null 2>&1; then
# GNU date
date "$@"
else
# BSD date
# Convert GNU-style arguments to BSD equivalents
case "$1" in
-d*)
local timestamp="${1#-d}"
shift
date -r "$timestamp" "$@"
;;
*)
date "$@"
;;
esac
fi
}
# Main script logic here
main() {
echo "Running on $(uname) system"
# Use safe_* functions for cross-platform compatibility
}
main "$@"
|
🧾 See Also