Extrae el nombre de archivo y la extensión en Bash

Quiero obtener el nombre de archivo (sin extensión) y la extensión por separado.

La mejor solución que encontré hasta ahora es:

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

Esto es incorrecto porque no funciona si el nombre del archivo contiene múltiples . caracteres. Si, digamos, tengo abjs , considerará a y b.js , en lugar de ab y js .

Se puede hacer fácilmente en Python con

 file, ext = os.path.splitext(path) 

pero preferiría no iniciar un intérprete de Python solo para esto, si es posible.

Alguna mejor idea?

Primero, obtenga el nombre del archivo sin la ruta:

 filename=$(basename -- "$fullfile") extension="${filename##*.}" filename="${filename%.*}" 

Alternativamente, puede enfocarse en el último ‘/’ de la ruta en lugar del ‘.’ que debería funcionar incluso si tiene extensiones de archivo impredecibles:

 filename="${fullfile##*/}" 
 ~% FILE="example.tar.gz" ~% echo "${FILE%%.*}" example ~% echo "${FILE%.*}" example.tar ~% echo "${FILE#*.}" tar.gz ~% echo "${FILE##*.}" gz 

Para obtener más detalles, consulte la expansión de parámetros de shell en el manual de Bash.

Por lo general, ya conoce la extensión, por lo que es posible que desee utilizar:

 basename filename .extension 

por ejemplo:

 basename /path/to/dir/filename.txt .txt 

y obtenemos

 filename 

Puedes usar la magia de las variables POSIX:

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

Hay una advertencia en que si su nombre de archivo era de la forma ./somefile.tar.gz , echo ${FILENAME%%.*} Eliminaría con avidez la coincidencia más larga con . y tendrías la cadena vacía

(Puede solucionarlo con una variable temporal:

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

)


Este sitio explica más.

 ${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 

Eso no parece funcionar si el archivo no tiene extensión, o no tiene nombre de archivo. Esto es lo que estoy usando; solo usa builtins y maneja más (pero no todos) nombres de archivos patológicos.

 #!/bin/bash for fullpath in "$@" do filename="${fullpath##*/}" # Strip longest match of */ from start dir="${fullpath:0:${#fullpath} - ${#filename}}" # Substring from 0 thru pos of filename base="${filename%.[^.]*}" # Strip shortest match of . plus at least one non-dot char from end ext="${filename:${#base} + 1}" # Substring from len of base thru end if [[ -z "$base" && -n "$ext" ]]; then # If we have an extension and no base, it's really the base base=".$ext" ext="" fi echo -e "$fullpath:\n\tdir = \"$dir\"\n\tbase = \"$base\"\n\text = \"$ext\"" done 

Y aquí hay algunos casos de prueba:

 $ basename-and-extension.sh / / home / me / / home / me / file /home/me/file.tar /home/me/file.tar.gz /home/me/.hidden / home / me / .hidden.tar / home / me / ...
 /:
     dir = "/"
     base = ""
     ext = ""
 /casa/:
     dir = "/ home / me /"
     base = ""
     ext = ""
 / home / me / file:
     dir = "/ home / me /"
     base = "archivo"
     ext = ""
 /home/me/file.tar:
     dir = "/ home / me /"
     base = "archivo"
     ext = "tar"
 /home/me/file.tar.gz:
     dir = "/ home / me /"
     base = "archivo.tar"
     ext = "gz"
 /home/me/.hidden:
     dir = "/ home / me /"
     base = ".hidden"
     ext = ""
 /home/me/.hidden.tar:
     dir = "/ home / me /"
     base = ".hidden"
     ext = "tar"
 /casa/..:
     dir = "/ home / me /"
     base = ".."
     ext = ""
 .:
     dir = ""
     base = "."
     ext = ""

Puedes usar basename .

Ejemplo:

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

Necesitas proporcionar un nombre de base con la extensión que se eliminará, sin embargo, si siempre estás ejecutando tar con -z entonces sabes que la extensión será .tar.gz .

Esto debería hacer lo que quieras:

 tar -zxvf $1 cd $(basename $1 .tar.gz) 
 pax> echo abjs | sed 's/\.[^.]*$//' ab pax> echo abjs | sed 's/^.*\.//' js 

funciona bien, así que puedes usar:

 pax> FILE=abjs pax> NAME=$(echo "$FILE" | sed 's/\.[^.]*$//') pax> EXTENSION=$(echo "$FILE" | sed 's/^.*\.//') pax> echo $NAME ab pax> echo $EXTENSION js 

Los comandos, por cierto, funcionan de la siguiente manera.

El comando para NAME sustituye a "." carácter seguido de cualquier cantidad de "." no "." caracteres hasta el final de la línea, sin nada (es decir, elimina todo, desde el final "." hasta el final de la línea, inclusive). Esto es básicamente una sustitución no codiciosa utilizando trucos regex.

El comando para EXTENSION sustituye un número cualquiera de caracteres seguido de un "." carácter al principio de la línea, sin nada (es decir, elimina todo desde el inicio de la línea hasta el punto final, inclusive). Esta es una sustitución codiciosa que es la acción predeterminada.

Mellen escribe en un comentario en una publicación de blog:

Usando Bash, también hay ${file%.*} Para obtener el nombre de archivo sin la extensión y ${file##*.} Para obtener la extensión solo. Es decir,

 file="thisfile.txt" echo "filename: ${file%.*}" echo "extension: ${file##*.}" 

Productos:

 filename: thisfile extension: txt 

Puede usar el comando cut para eliminar las dos últimas extensiones (la parte ".tar.gz" ):

 $ echo "foo.tar.gz" | cut -d'.' --complement -f2- foo 

Como señaló Clayton Hughes en un comentario, esto no funcionará para el ejemplo real en la pregunta. Entonces, como alternativa, propongo usar sed con expresiones regulares extendidas, como esta:

 $ echo "mpc-1.0.1.tar.gz" | sed -r 's/\.[[:alnum:]]+\.[[:alnum:]]+$//' mpc-1.0.1 

Funciona al eliminar las últimas dos extensiones (alfanuméricas) incondicionalmente.

[Actualizado nuevamente después del comentario de Anders Lindahl]

No es necesario molestarse con awk o sed o incluso perl para esta simple tarea. Hay una solución compatible con Bash puro, os.path.splitext() que solo usa expansiones de parámetros.

Implementación de referencia

Documentación de os.path.splitext(path) :

Divida la ruta del nombre de ruta en un par (root, ext) manera que root + ext == path , y ext esté vacía o comience con un punto y contenga como máximo un período. Los períodos iniciales en el nombre base se ignoran; splitext('.cshrc') devuelve ('.cshrc', '') .

Código de Python:

 root, ext = os.path.splitext(path) 

Implementación Bash

Honrando los principales períodos

 root="${path%.*}" ext="${path#"$root"}" 

Ignorando los períodos principales

 root="${path#.}";root="${path%"$root"}${root%.*}" ext="${path#"$root"}" 

Pruebas

Aquí hay casos de prueba para la implementación Ignorar períodos principales , que debe coincidir con la implementación de referencia de Python en cada entrada.

 |---------------|-----------|-------| |path |root |ext | |---------------|-----------|-------| |' .txt' |' ' |'.txt' | |' .txt.txt' |' .txt' |'.txt' | |' txt' |' txt' |'' | |'*.txt.txt' |'*.txt' |'.txt' | |'.cshrc' |'.cshrc' |'' | |'.txt' |'.txt' |'' | |'?.txt.txt' |'?.txt' |'.txt' | |'\n.txt.txt' |'\n.txt' |'.txt' | |'\t.txt.txt' |'\t.txt' |'.txt' | |'a b.txt.txt' |'a b.txt' |'.txt' | |'a*b.txt.txt' |'a*b.txt' |'.txt' | |'a?b.txt.txt' |'a?b.txt' |'.txt' | |'a\nb.txt.txt' |'a\nb.txt' |'.txt' | |'a\tb.txt.txt' |'a\tb.txt' |'.txt' | |'txt' |'txt' |'' | |'txt.pdf' |'txt' |'.pdf' | |'txt.tar.gz' |'txt.tar' |'.gz' | |'txt.txt' |'txt' |'.txt' | |---------------|-----------|-------| 

Resultados de la prueba

Todas las pruebas pasaron

Estas son algunas sugerencias alternativas (principalmente en awk ), que incluyen algunos casos de uso avanzados, como la extracción de números de versión para paquetes de software.

 f='/path/to/complex/file.1.0.1.tar.gz' # Filename : 'file.1.0.x.tar.gz' echo "$f" | awk -F'/' '{print $NF}' # Extension (last): 'gz' echo "$f" | awk -F'[.]' '{print $NF}' # Extension (all) : '1.0.1.tar.gz' echo "$f" | awk '{sub(/[^.]*[.]/, "", $0)} 1' # Extension (last-2): 'tar.gz' echo "$f" | awk -F'[.]' '{print $(NF-1)"."$NF}' # Basename : 'file' echo "$f" | awk '{gsub(/.*[/]|[.].*/, "", $0)} 1' # Basename-extended : 'file.1.0.1.tar' echo "$f" | awk '{gsub(/.*[/]|[.]{1}[^.]+$/, "", $0)} 1' # Path : '/path/to/complex/' echo "$f" | awk '{match($0, /.*[/]/, a); print a[0]}' # or echo "$f" | grep -Eo '.*[/]' # Folder (containing the file) : 'complex' echo "$f" | awk -F'/' '{$1=""; print $(NF-1)}' # Version : '1.0.1' # Defined as 'number.number' or 'number.number.number' echo "$f" | grep -Eo '[0-9]+[.]+[0-9]+[.]?[0-9]?' # Version - major : '1' echo "$f" | grep -Eo '[0-9]+[.]+[0-9]+[.]?[0-9]?' | cut -d. -f1 # Version - minor : '0' echo "$f" | grep -Eo '[0-9]+[.]+[0-9]+[.]?[0-9]?' | cut -d. -f2 # Version - patch : '1' echo "$f" | grep -Eo '[0-9]+[.]+[0-9]+[.]?[0-9]?' | cut -d. -f3 # All Components : "path to complex file 1 0 1 tar gz" echo "$f" | awk -F'[/.]' '{$1=""; print $0}' # Is absolute : True (exit-code : 0) # Return true if it is an absolute path (starting with '/' or '~/' echo "$f" | grep -q '^[/]\|^~/' 

Todos los casos de uso están utilizando la ruta completa original como entrada, sin depender de los resultados intermedios.

Creo que si solo necesitas el nombre del archivo, puedes probar esto:

 FULLPATH=/usr/share/X11/xorg.conf.d/50-synaptics.conf # Remove all the prefix until the "/" character FILENAME=${FULLPATH##*/} # Remove all the prefix until the "." character FILEEXTENSION=${FILENAME##*.} # Remove a suffix, in our case, the filename. This will return the name of the directory that contains this file. BASEDIRECTORY=${FULLPATH%$FILENAME} echo "path = $FULLPATH" echo "file name = $FILENAME" echo "file extension = $FILEEXTENSION" echo "base directory = $BASEDIRECTORY" 

Y eso es todo = D.

[Revisado a partir de una función de una línea genérica bash, el comportamiento ahora es coherente con las utilidades dirname y basename ; razonamiento agregado.]

La respuesta aceptada funciona bien en casos típicos , pero falla en casos extremos, a saber:

  • Para los nombres de archivo sin extensión (llamado sufijo en el rest de esta respuesta), extension=${filename##*.} Devuelve el nombre de archivo de entrada en lugar de una cadena vacía.
  • extension=${filename##*.} no incluye la inicial . , contrario a la convención.
    • Ciegos antepuestos . no funcionaría para nombres de archivos sin sufijo.
  • filename="${filename%.*}" será la cadena vacía, si el nombre del archivo de entrada comienza . y no contiene más . caracteres (por ejemplo, .bash_profile ) – contrario a la convención.

———

Por lo tanto, la complejidad de una solución robusta que cubra todos los casos extremos requiere una función ; consulte su definición a continuación; puede devolver todos los componentes de una ruta .

Ejemplo de llamada:

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

Tenga en cuenta que los argumentos después de la ruta de entrada se eligen libremente, nombres de variables posicionales.
Para omitir las variables que no sean de interés anteriores a las que sí lo son, especifique _ (para usar la variable $_ ) o '' ; por ejemplo, para extraer la raíz y la extensión del nombre de archivo solamente, use la extensión 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 

Código de prueba que ejerce la función:

 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 

Salida esperada: tenga en cuenta los casos extremos:

  • un nombre de archivo sin sufijo
  • un nombre de archivo que comienza con . ( no se considera el inicio del sufijo)
  • una ruta de entrada que termina en / (final / se ignora)
  • una ruta de entrada que es un nombre de archivo solamente (.se devuelve como ruta principal)
  • un nombre de archivo que tiene más de . – token prefijado (solo el último se considera el sufijo):
 ----- /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 

La solución más pequeña y simple (en una sola línea) es:

$ file=/blaabla/bla/blah/foo.txt

echo $(basename ${file%.*})

foo

Puede forzar corte para mostrar todos los campos y los siguientes sumndo - al número de campo.

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

Entonces, si FILE es eth0.pcap.gz , la EXTENSIÓN será pcap.gz

Usando la misma lógica, también puede buscar el nombre del archivo usando ‘-‘ con corte de la siguiente manera:

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

Esto funciona incluso para nombres de archivo que no tienen ninguna extensión.

Ok, si entiendo correctamente, el problema aquí es cómo obtener el nombre y la extensión completa de un archivo que tiene múltiples extensiones, por ejemplo, stuff.tar.gz .

Esto funciona para mí:

 fullfile="stuff.tar.gz" fileExt=${fullfile#*.} fileName=${fullfile%*.$fileExt} 

Esto le dará stuff como nombre de archivo y .tar.gz como extensión. Funciona para cualquier cantidad de extensiones, incluidas 0. Espero que esto ayude a cualquiera que tenga el mismo problema =)

Reconocimiento de archivos mágicos

Además de la cantidad de buenas respuestas en esta pregunta sobre el desbordamiento de stack, me gustaría añadir:

En Linux y otros unixen, hay un comando mágico llamado file , que hace detección de tipo de archivo mediante el análisis de algunos primeros bytes de archivo. Esta es una herramienta muy antigua, utilizada inicialmente para servidores de impresión (si no se creó para … no estoy seguro de eso).

 file myfile.txt myfile.txt: UTF-8 Unicode text file -b --mime-type myfile.txt text/plain 

Las extensiones de estándares se pueden encontrar en /etc/mime.types (en mi escritorio Debian GNU / Linux. Consulte man file y man mime.types . Quizás tenga que instalar la utilidad de file y mime-support paquetes mime-support ):

 grep $( file -b --mime-type myfile.txt )  

Puede crear una función bash para determinar la extensión correcta. Hay una pequeña muestra (no perfecta):

 file2ext() { local _mimetype=$(file -Lb --mime-type "$1") _line _basemimetype case ${_mimetype##*[/.-]} in gzip | bzip2 | xz | z ) _mimetype=${_mimetype##*[/.-]} _mimetype=${_mimetype//ip} _basemimetype=$(file -zLb --mime-type "$1") ;; stream ) _mimetype=($(file -Lb "$1")) [ "${_mimetype[1]}" = "compressed" ] && _basemimetype=$(file -b --mime-type - < <( ${_mimetype,,} -d <"$1")) || _basemimetype=${_mimetype,,} _mimetype=${_mimetype,,} ;; executable ) _mimetype='' _basemimetype='' ;; dosexec ) _mimetype='' _basemimetype='exe' ;; shellscript ) _mimetype='' _basemimetype='sh' ;; * ) _basemimetype=$_mimetype _mimetype='' ;; esac while read -a _line ;do if [ "$_line" == "$_basemimetype" ] ;then [ "$_line[1]" ] && _basemimetype=${_line[1]} || _basemimetype=${_basemimetype##*[/.-]} break fi done  

Esta función podría establecer una variable Bash que se puede usar más adelante:

(Esto está inspirado en la respuesta correcta de @Petesh):

 filename=$(basename "$fullfile") filename="${filename%.*}" file2ext "$fullfile" extension echo "$fullfile -> $filename . $extension" 
 $ F = "text file.test.txt" $ echo ${F/*./} txt 

Esto sirve para múltiples puntos y espacios en un nombre de archivo, sin embargo, si no hay extensión, devuelve el nombre del archivo. Fácil de verificar; solo prueba que el nombre de archivo y la extensión sean los mismos.

Naturalmente, este método no funciona para los archivos .tar.gz. Sin embargo, eso podría manejarse en un proceso de dos pasos. Si la extensión es gz, compruebe nuevamente si también hay una extensión tar.

Yo uso la siguiente secuencia de comandos

 $ echo "foo.tar.gz"|rev|cut -d"." -f3-|rev foo 

Cómo extraer el nombre de archivo y la extensión en fish :

 function split-filename-extension --description "Prints the filename and extension" for file in $argv if test -f $file set --local extension (echo $file | awk -F. '{print $NF}') set --local filename (basename $file .$extension) echo "$filename $extension" else echo "$file is not a valid file" end end end 

Advertencias: Se divide en el último punto, que funciona bien para nombres de archivos con puntos en ellos, pero no es bueno para extensiones con puntos en ellos. Vea el ejemplo a continuación.

Uso:

 $ split-filename-extension foo-0.4.2.zip bar.tar.gz foo-0.4.2 zip # Looks good! bar.tar gz # Careful, you probably want .tar.gz as the extension. 

Probablemente hay mejores formas de hacer esto. Siéntete libre de editar mi respuesta para mejorarla.


Si hay un conjunto limitado de extensiones que tratará y las conoce a todas, intente esto:

 switch $file case *.tar echo (basename $file .tar) tar case *.tar.bz2 echo (basename $file .tar.bz2) tar.bz2 case *.tar.gz echo (basename $file .tar.gz) tar.gz # and so on end 

Esto no tiene la advertencia como el primer ejemplo, pero debe manejar cada caso, por lo que podría ser más tedioso dependiendo de la cantidad de extensiones que pueda esperar.

Aquí está el código con AWK . Se puede hacer de manera más simple. Pero no soy bueno en AWK.

 filename$ ls abc.a.txt abctxt pp-kk.txt filename$ find . -type f | awk -F/ '{print $2}' | rev | awk -F"." '{$1="";print}' | rev | awk 'gsub(" ",".") ,sub(".$", "")' abc.a abc pp-kk filename$ find . -type f | awk -F/ '{print $2}' | awk -F"." '{print $NF}' txt txt txt 

Una respuesta simple:

Para ampliar la respuesta de las variables POSIX , tenga en cuenta que puede hacer patrones más interesantes. Entonces, para el caso detallado aquí, simplemente puede hacer esto:

 tar -zxvf $1 cd ${1%.tar.*} 

Eso cortará la última aparición de .tar. .

De manera más general, si desea eliminar la última ocurrencia de. . entonces

 ${1.*.*} 

debería funcionar bien

El enlace de la respuesta anterior parece estar muerto. Aquí hay una gran explicación de un montón de manipulación de cadenas que puedes hacer directamente en Bash, desde TLDP .

A partir de la respuesta de Petesh , si solo se necesita el nombre de archivo, tanto la ruta como la extensión se pueden eliminar en una sola línea,

 filename=$(basename ${fullname%.*}) 

Simply use ${parameter%word}

En tu caso:

 ${FILE%.*} 

If you want to test it, all following work, and just remove the extension:

 FILE=abc.xyz; echo ${FILE%.*}; FILE=123.abc.xyz; echo ${FILE%.*}; FILE=abc; echo ${FILE%.*}; 

Based largely off of @mklement0’s excellent, and chock-full of random, useful bashisms – as well as other answers to this / other questions / “that darn internet”… I wrapped it all up in a little, slightly more comprehensible, reusable function for my (or your) .bash_profile that takes care of what (I consider) should be a more robust version of dirname / basename / what have you ..

 function path { SAVEIFS=$IFS; IFS="" # stash IFS for safe-keeping, etc. [[ $# != 2 ]] && echo "usage: path  " && return # demand 2 arguments [[ $1 =~ ^(.*/)?(.+)?$ ]] && { # regex parse the path dir=${BASH_REMATCH[1]} file=${BASH_REMATCH[2]} ext=$([[ $file = *.* ]] && printf %s ${file##*.} || printf '') # edge cases for extesionless files and files like ".nesh_profile.coffee" [[ $file == $ext ]] && fnr=$file && ext='' || fnr=${file:0:$((${#file}-${#ext}))} case "$2" in dir) echo "${dir%/*}"; ;; name) echo "${fnr%.*}"; ;; fullname) echo "${fnr%.*}.$ext"; ;; ext) echo "$ext"; ;; esac } IFS=$SAVEIFS } 

Usage examples…

 SOMEPATH=/path/to.some/.random\ file.gzip path $SOMEPATH dir # /path/to.some path $SOMEPATH name # .random file path $SOMEPATH ext # gzip path $SOMEPATH fullname # .random file.gzip path gobbledygook # usage: -bash   

From the answers above, the shortest oneliner to mimic Python’s

 file, ext = os.path.splitext(path) 

presuming your file really does have an extension, is

 EXT="${PATH##*.}"; FILE=$(basename "$PATH" .$EXT) 

If you also want to allow empty extensions, this is the shortest I could come up with:

 echo 'hello.txt' | sed -r 's/.+\.(.+)|.*/\1/' # EXTENSION echo 'hello.txt' | sed -r 's/(.+)\..+|(.*)/\1\2/' # FILENAME 

1st line explained: It matches PATH.EXT or ANYTHING and replaces it with EXT. If ANYTHING was matched, the ext group is not captured.

In order to make dir more useful (in the case a local file with no path is specified as input) I did the following:

 # Substring from 0 thru pos of filename dir="${fullpath:0:${#fullpath} - ${#filename}}" if [[ -z "$dir" ]]; then dir="./" fi 

This allows you to do something useful like add a suffix to the input file basename as:

 outfile=${dir}${base}_suffix.${ext} testcase: foo.bar dir: "./" base: "foo" ext: "bar" outfile: "./foo_suffix.bar" testcase: /home/me/foo.bar dir: "/home/me/" base: "foo" ext: "bar" outfile: "/home/me/foo_suffix.bar" 

Puedes usar

 sed 's/^/./' | rev | cut -d. -f2- | rev | cut -c2- 

to get file name and

 sed 's/^/./' | rev | cut -d. -f1 | rev 

to get extension.

Test case:

 echo "filename.gz" | sed 's/^/./' | rev | cut -d. -f2- | rev | cut -c2- echo "filename.gz" | sed 's/^/./' | rev | cut -d. -f1 | rev echo "filename" | sed 's/^/./' | rev | cut -d. -f2- | rev | cut -c2- echo "filename" | sed 's/^/./' | rev | cut -d. -f1 | rev echo "filename.tar.gz" | sed 's/^/./' | rev | cut -d. -f2- | rev | cut -c2- echo "filename.tar.gz" | sed 's/^/./' | rev | cut -d. -f1 | rev 

Here is the algorithm I used for finding the name and extension of a file when I wrote a Bash script to make names unique when names conflicted with respect to casing.

 #! /bin/bash # # Finds # -- name and extension pairs # -- null extension when there isn't an extension. # -- Finds name of a hidden file without an extension # declare -a fileNames=( '.Montreal' '.Rome.txt' 'Loundon.txt' 'Paris' 'San Diego.txt' 'San Francisco' ) echo "Script ${0} finding name and extension pairs." echo for theFileName in "${fileNames[@]}" do echo "theFileName=${theFileName}" # Get the proposed name by chopping off the extension name="${theFileName%.*}" # get extension. Set to null when there isn't an extension # Thanks to mklement0 in a comment above. extension=$([[ "$theFileName" == *.* ]] && echo ".${theFileName##*.}" || echo '') # a hidden file without extenson? if [ "${theFileName}" = "${extension}" ] ; then # hidden file without extension. Fixup. name=${theFileName} extension="" fi echo " name=${name}" echo " extension=${extension}" done 

The test run.

 $ config/Name\&Extension.bash Script config/Name&Extension.bash finding name and extension pairs. theFileName=.Montreal name=.Montreal extension= theFileName=.Rome.txt name=.Rome extension=.txt theFileName=Loundon.txt name=Loundon extension=.txt theFileName=Paris name=Paris extension= theFileName=San Diego.txt name=San Diego extension=.txt theFileName=San Francisco name=San Francisco extension= $ 

FYI: The complete transliteration program and more test cases can be found here: https://www.dropbox.com/s/4c6m0f2e28a1vxf/avoid-clashes-code.zip?dl=0