Przejdź do treści

🆚 BSD vs GNU Tools Reference

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

1
2
3
4
5
6
7
# 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/

🔧 Essential Tool Comparisons

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


 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


📊 Advanced Tool Comparisons

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

Tool Detection Functions

 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

🧠 Best Practices for Cross-Platform Scripts

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