Extract filename and extension in Bash

后端 未结 30 1967
野趣味
野趣味 2020-11-21 05:29

I want to get the filename (without extension) and the extension separately.

The best solution I found so far is:

NAME=`echo \"$FILE\" | cut -d\'.\'          


        
相关标签:
30条回答
  • 2020-11-21 06:32

    The accepted answer works well in typical cases, but fails in edge cases, namely:

    • For filenames without extension (called suffix in the remainder of this answer), extension=${filename##*.} returns the input filename rather than an empty string.
    • extension=${filename##*.} does not include the initial ., contrary to convention.
      • Blindly prepending . would not work for filenames without suffix.
    • filename="${filename%.*}" will be the empty string, if the input file name starts with . and contains no further . characters (e.g., .bash_profile) - contrary to convention.

    ---------

    Thus, the complexity of a robust solution that covers all edge cases calls for a function - see its definition below; it can return all components of a path.

    Example call:

    splitPath '/etc/bash.bashrc' dir fname fnameroot suffix
    # -> $dir == '/etc'
    # -> $fname == 'bash.bashrc'
    # -> $fnameroot == 'bash'
    # -> $suffix == '.bashrc'
    

    Note that the arguments after the input path are freely chosen, positional variable names.
    To skip variables not of interest that come before those that are, specify _ (to use throw-away variable $_) or ''; e.g., to extract filename root and extension only, use splitPath '/etc/bash.bashrc' _ _ fnameroot extension.


    # SYNOPSIS
    #   splitPath path varDirname [varBasename [varBasenameRoot [varSuffix]]] 
    # DESCRIPTION
    #   Splits the specified input path into its components and returns them by assigning
    #   them to variables with the specified *names*.
    #   Specify '' or throw-away variable _ to skip earlier variables, if necessary.
    #   The filename suffix, if any, always starts with '.' - only the *last*
    #   '.'-prefixed token is reported as the suffix.
    #   As with `dirname`, varDirname will report '.' (current dir) for input paths
    #   that are mere filenames, and '/' for the root dir.
    #   As with `dirname` and `basename`, a trailing '/' in the input path is ignored.
    #   A '.' as the very first char. of a filename is NOT considered the beginning
    #   of a filename suffix.
    # EXAMPLE
    #   splitPath '/home/jdoe/readme.txt' parentpath fname fnameroot suffix
    #   echo "$parentpath" # -> '/home/jdoe'
    #   echo "$fname" # -> 'readme.txt'
    #   echo "$fnameroot" # -> 'readme'
    #   echo "$suffix" # -> '.txt'
    #   ---
    #   splitPath '/home/jdoe/readme.txt' _ _ fnameroot
    #   echo "$fnameroot" # -> 'readme'  
    splitPath() {
      local _sp_dirname= _sp_basename= _sp_basename_root= _sp_suffix=
        # simple argument validation
      (( $# >= 2 )) || { echo "$FUNCNAME: ERROR: Specify an input path and at least 1 output variable name." >&2; exit 2; }
        # extract dirname (parent path) and basename (filename)
      _sp_dirname=$(dirname "$1")
      _sp_basename=$(basename "$1")
        # determine suffix, if any
      _sp_suffix=$([[ $_sp_basename = *.* ]] && printf %s ".${_sp_basename##*.}" || printf '')
        # determine basename root (filemane w/o suffix)
      if [[ "$_sp_basename" == "$_sp_suffix" ]]; then # does filename start with '.'?
          _sp_basename_root=$_sp_basename
          _sp_suffix=''
      else # strip suffix from filename
        _sp_basename_root=${_sp_basename%$_sp_suffix}
      fi
      # assign to output vars.
      [[ -n $2 ]] && printf -v "$2" "$_sp_dirname"
      [[ -n $3 ]] && printf -v "$3" "$_sp_basename"
      [[ -n $4 ]] && printf -v "$4" "$_sp_basename_root"
      [[ -n $5 ]] && printf -v "$5" "$_sp_suffix"
      return 0
    }
    
    test_paths=(
      '/etc/bash.bashrc'
      '/usr/bin/grep'
      '/Users/jdoe/.bash_profile'
      '/Library/Application Support/'
      'readme.new.txt'
    )
    
    for p in "${test_paths[@]}"; do
      echo ----- "$p"
      parentpath= fname= fnameroot= suffix=
      splitPath "$p" parentpath fname fnameroot suffix
      for n in parentpath fname fnameroot suffix; do
        echo "$n=${!n}"
      done
    done
    

    Test code that exercises the function:

    test_paths=(
      '/etc/bash.bashrc'
      '/usr/bin/grep'
      '/Users/jdoe/.bash_profile'
      '/Library/Application Support/'
      'readme.new.txt'
    )
    
    for p in "${test_paths[@]}"; do
      echo ----- "$p"
      parentpath= fname= fnameroot= suffix=
      splitPath "$p" parentpath fname fnameroot suffix
      for n in parentpath fname fnameroot suffix; do
        echo "$n=${!n}"
      done
    done
    

    Expected output - note the edge cases:

    • a filename having no suffix
    • a filename starting with . (not considered the start of the suffix)
    • an input path ending in / (trailing / is ignored)
    • an input path that is a filename only (. is returned as the parent path)
    • a filename that has more than .-prefixed token (only the last is considered the suffix):
    ----- /etc/bash.bashrc
    parentpath=/etc
    fname=bash.bashrc
    fnameroot=bash
    suffix=.bashrc
    ----- /usr/bin/grep
    parentpath=/usr/bin
    fname=grep
    fnameroot=grep
    suffix=
    ----- /Users/jdoe/.bash_profile
    parentpath=/Users/jdoe
    fname=.bash_profile
    fnameroot=.bash_profile
    suffix=
    ----- /Library/Application Support/
    parentpath=/Library
    fname=Application Support
    fnameroot=Application Support
    suffix=
    ----- readme.new.txt
    parentpath=.
    fname=readme.new.txt
    fnameroot=readme.new
    suffix=.txt
    
    0 讨论(0)
  • 2020-11-21 06:32

    Using example file /Users/Jonathan/Scripts/bash/MyScript.sh, this code:

    MY_EXT=".${0##*.}"
    ME=$(/usr/bin/basename "${0}" "${MY_EXT}")
    

    will result in ${ME} being MyScript and ${MY_EXT} being .sh:


    Script:

    #!/bin/bash
    set -e
    
    MY_EXT=".${0##*.}"
    ME=$(/usr/bin/basename "${0}" "${MY_EXT}")
    
    echo "${ME} - ${MY_EXT}"
    

    Some tests:

    $ ./MyScript.sh 
    MyScript - .sh
    
    $ bash MyScript.sh
    MyScript - .sh
    
    $ /Users/Jonathan/Scripts/bash/MyScript.sh
    MyScript - .sh
    
    $ bash /Users/Jonathan/Scripts/bash/MyScript.sh
    MyScript - .sh
    
    0 讨论(0)
  • 2020-11-21 06:34

    You can use the magic of POSIX parameter expansion:

    bash-3.2$ FILENAME=somefile.tar.gz
    bash-3.2$ echo "${FILENAME%%.*}"
    somefile
    bash-3.2$ echo "${FILENAME%.*}"
    somefile.tar
    

    There's a caveat in that if your filename was of the form ./somefile.tar.gz then echo ${FILENAME%%.*} would greedily remove the longest match to the . and you'd have the empty string.

    (You can work around that with a temporary variable:

    FULL_FILENAME=$FILENAME
    FILENAME=${FULL_FILENAME##*/}
    echo ${FILENAME%%.*}
    

    )


    This site explains more.

    ${variable%pattern}
      Trim the shortest match from the end
    ${variable##pattern}
      Trim the longest match from the beginning
    ${variable%%pattern}
      Trim the longest match from the end
    ${variable#pattern}
      Trim the shortest match from the beginning
    
    0 讨论(0)
  • 2020-11-21 06:34
    $ F = "text file.test.txt"  
    $ echo ${F/*./}  
    txt  
    

    This caters for multiple dots and spaces in a filename, however if there is no extension it returns the filename itself. Easy to check for though; just test for the filename and extension being the same.

    Naturally this method doesn't work for .tar.gz files. However that could be handled in a two step process. If the extension is gz then check again to see if there is also a tar extension.

    0 讨论(0)
  • 2020-11-21 06:35

    You can use basename.

    Example:

    $ basename foo-bar.tar.gz .tar.gz
    foo-bar
    

    You do need to provide basename with the extension that shall be removed, however if you are always executing tar with -z then you know the extension will be .tar.gz.

    This should do what you want:

    tar -zxvf $1
    cd $(basename $1 .tar.gz)
    
    0 讨论(0)
  • 2020-11-21 06:35

    You can force cut to display all fields and subsequent ones adding - to field number.

    NAME=`basename "$FILE"`
    EXTENSION=`echo "$NAME" | cut -d'.' -f2-`
    

    So if FILE is eth0.pcap.gz, the EXTENSION will be pcap.gz

    Using the same logic, you can also fetch the file name using '-' with cut as follows :

    NAME=`basename "$FILE" | cut -d'.' -f-1`
    

    This works even for filenames that do not have any extension.

    0 讨论(0)
提交回复
热议问题