To extract the filename and its extension in Bash, use parameter expansion for efficiency and simplicity. Here’s a detailed guide covering edge cases like hidden files, filenames with no extensions, and multiple dots:
Method 1: Parameter Expansion (Recommended)
Bash’s built-in string manipulation is the fastest and most reliable approach.
1. Extract Filename Without Extension
filename="document.tar.gz"
name="${filename%.*}"       # Removes the shortest suffix after the last '.'
echo "$name"                # Output: document.tar2. Extract Extension
extension="${filename##*.}" # Removes the longest prefix up to the last '.'
echo "$extension"           # Output: gz3. Handle Full Paths
If working with a full path (e.g., /home/user/docs/file.txt):
fullpath="/home/user/docs/file.txt"
filename="${fullpath##*/}"  # Extract filename: file.txt
name="${filename%.*}"       # Remove extension: file
extension="${filename##*.}" # Extract extension: txtEdge Cases
Case 1: Filename with No Extension
filename="README"
name="${filename%.*}"       # Output: README
extension="${filename##*.}" # Output: README (incorrect)Fix: Check if the filename contains a .:
if [[ "$filename" == *.* ]]; then
  name="${filename%.*}"
  extension="${filename##*.}"
else
  name="$filename"
  extension=""
fiCase 2: Hidden Files (e.g., .bashrc)
Hidden files (starting with .) typically have no extension:
filename=".bashrc"
if [[ "$filename" =~ ^\..+ && "$filename" != *.* ]]; then
  name="$filename"
  extension=""
else
  name="${filename%.*}"
  extension="${filename##*.}"
fiCase 3: Multiple Dots (e.g., image.version1.2.jpg)
The last dot determines the extension:
filename="image.version1.2.jpg"
name="${filename%.*}"       # Output: image.version1.2
extension="${filename##*.}" # Output: jpgMethod 2: Using basename and dirname
For paths, combine basename and parameter expansion:
fullpath="/var/log/app.log"
filename=$(basename "$fullpath")   # Output: app.log
name="${filename%.*}"              # Output: app
extension="${filename##*.}"        # Output: logMethod 3: awk for Advanced Splitting
Use awk to handle complex patterns:
filename="data.2023.backup.tar.gz"
extension=$(awk -F. '{print $NF}' <<< "$filename")  # Output: gz
name=$(awk 'sub(/\.[^.]*$/, "")' <<< "$filename")   # Output: data.2023.backup.tarMethod 4: sed for Regex Parsing
Use regular expressions with sed:
filename="archive.tar.gz"
name=$(sed 's/\.[^.]*$//' <<< "$filename")     # Output: archive.tar
extension=$(sed 's/.*\.//' <<< "$filename")    # Output: gzComplete Script Example
A reusable function to handle all cases:
split_filename() {
  local fullpath="$1"
  local filename="${fullpath##*/}"  # Extract filename from path
  local name extension
  if [[ "$filename" =~ ^\..+ && "$filename" != *.* ]]; then
    # Hidden file with no extension (e.g., .bashrc)
    name="$filename"
    extension=""
  elif [[ "$filename" == *.* ]]; then
    # Split into name and extension
    name="${filename%.*}"
    extension="${filename##*.}"
  else
    # No extension
    name="$filename"
    extension=""
  fi
  echo "Filename: $name"
  echo "Extension: ${extension:-none}"
}
# Test Cases
split_filename "/home/user/docs/file.txt"      # Filename: file, Extension: txt
split_filename "image.png"                    # Filename: image, Extension: png
split_filename ".bashrc"                      # Filename: .bashrc, Extension: none
split_filename "archive.tar.gz"               # Filename: archive.tar, Extension: gz
split_filename "README"                       # Filename: README, Extension: noneKey Notes
- Parameter Expansion is the most efficient method (no external commands).
- Hidden Files: Treat files starting with .as having no extension.
- No Extension: Return an empty string if no .exists.
- Multiple Dots: The last dot defines the extension (e.g., file.tar.gz→gz).
Use these methods to reliably parse filenames in your Bash scripts!