web-dev-qa-db-fra.com

Extraire le nom de fichier et l'extension dans Bash

Je veux obtenir le nom de fichier (sans extension) et l'extension séparément.

La meilleure solution que j'ai trouvée jusqu'à présent est:

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

Cela est faux car cela ne fonctionne pas si le nom du fichier contient plusieurs caractères .. Si, par exemple, j'ai a.b.js, il considérera a et b.js au lieu de a.b et js.

Cela peut être facilement fait en Python avec

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

mais je préférerais ne pas lancer un interprète Python uniquement pour cela, si possible.

De meilleures idées?

1905
ibz

Commencez par obtenir le nom du fichier sans le chemin:

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

Alternativement, vous pouvez vous concentrer sur le dernier '/' du chemin au lieu du '.' ce qui devrait fonctionner même si vous avez des extensions de fichier imprévisibles:

filename="${fullfile##*/}"

Vous voudrez peut-être vérifier la documentation:

3223
Petesh
~% FILE="example.tar.gz"
~% echo "${FILE%%.*}"
example
~% echo "${FILE%.*}"
example.tar
~% echo "${FILE#*.}"
tar.gz
~% echo "${FILE##*.}"
gz

Pour plus de détails, voir expansion des paramètres du shell dans le manuel Bash.

589
Juliano

Habituellement, vous connaissez déjà l'extension, vous pouvez donc utiliser:

basename filename .extension

par exemple:

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

et nous obtenons

filename
355
Tomi Po

Vous pouvez utiliser la magie des variables POSIX:

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

Il y a une mise en garde dans le fait que si votre nom de fichier était de la forme ./somefile.tar.gz, alors echo ${FILENAME%%.*} effacerait goulûment la correspondance la plus longue avec le . et vous auriez la chaîne vide.

(Vous pouvez contourner ce problème avec une variable temporaire:

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

)


Ce site explique plus.

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

Cela ne semble pas fonctionner si le fichier n'a pas d'extension ou de nom de fichier. Voici ce que j'utilise. il utilise uniquement les commandes intégrées et gère plus de noms de fichiers pathologiques (mais pas tous).

#!/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

Et voici quelques cas de test:

 $ basename-and-extension.sh// home/me// home/me/fichier /home/me/file.tar /home/me/file.tar.gz /home/me/.hidden /home/me/.hidden.tar/home/me/.... 
 /: 
 dir = "/"
 base =" "
 ext = "" 
/home/me /:
 dir = "/home/me/"
 base =" "
 ext =" "
 /home/me/file:
 dir = "/home/me/"
 base =" fichier "
 ext =" "
/home/me/file.tar: 
 dir = "/home/me/"
 base =" fichier "
 ext =" tar "
/home/me/file. tar.gz: 
 dir = "/home/me/"
 base =" fichier.tar "
 ext =" gz "
/home/me/.hidden: 
 dir = "/home/me/"
 base =" .hidden "
 ext =" "
/home/me/.hidden. tar: 
 dir = "/home/me/"
 base =" .hidden "
 ext =" tar "
/home/me/..: 
 dir = "/home/me/"[____.] base =" .. "
 ext =" "
.: 
 dir =" " 
 base = "." 
 ext = "" 
68
Doctor J

Vous pouvez utiliser basename .

Exemple:

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

Vous devez fournir le nom de base avec l'extension à supprimer. Toutefois, si vous exécutez toujours tar avec -z, vous savez que l'extension sera .tar.gz.

Cela devrait faire ce que vous voulez:

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

fonctionne bien, vous pouvez donc simplement utiliser:

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

Les commandes, en passant, fonctionnent comme suit.

La commande pour NAME remplace un caractère "." suivi d'un nombre quelconque de caractères non -"." jusqu'à la fin de la ligne, sans rien (c'est-à-dire qu'il supprime tout du dernier "." jusqu'à la fin de la ligne, inclus). Il s’agit essentiellement d’une substitution non gourmande utilisant la ruse regex.

La commande pour EXTENSION remplace un nombre quelconque de caractères suivis par un caractère "." au début de la ligne, sans rien (c'est-à-dire qu'elle supprime tout du début à la dernière ligne, y compris ). C'est une substitution gloutonne qui est l'action par défaut.

31
paxdiablo

Mellen écrit dans un commentaire sur un article de blog:

En utilisant Bash, il y a aussi ${file%.*} pour obtenir le nom du fichier sans extension et ${file##*.} pour obtenir l’extension seule. C'est,

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

Les sorties:

filename: thisfile
extension: txt
27
Kebabbert

Vous pouvez utiliser la commande cut pour supprimer les deux dernières extensions (la partie ".tar.gz"):

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

Comme Clayton Hughes l'a noté dans un commentaire, cela ne fonctionnera pas pour l'exemple présenté dans la question. Donc, comme alternative, je propose d'utiliser sed avec des expressions régulières étendues, comme ceci:

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

Cela fonctionne en supprimant les deux dernières extensions (alphanumériques) sans condition.

[Mis à jour à nouveau après le commentaire de Anders Lindahl]

25

Pas besoin de s'embêter avec awk ou sed ou même Perl pour cette tâche simple. Il existe une solution compatible pure-Bash, os.path.splitext(), qui utilise uniquement des extensions de paramètres.

Mise en oeuvre de référence

Documentation de os.path.splitext(path) :

Scindez le chemin du chemin d'accès en une paire (root, ext) de telle sorte que root + ext == path, et ext est vide ou commence par un point et contient la plupart une période. Les périodes principales du nom de base sont ignorées; splitext('.cshrc') renvoie ('.cshrc', '').

Code Python:

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

Implémentation Bash

Respecter les périodes principales

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

Ignorer les périodes principales

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

Des tests

Voici des cas de test pour l'implémentation en ignorant les périodes principales , qui doit correspondre à l'implémentation de référence Python sur chaque entrée.

|---------------|-----------|-------|
|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' |
|---------------|-----------|-------|

Résultats de test

Tous les tests ont réussi.

25
Cyker

Voici quelques suggestions alternatives (principalement dans awk), y compris des cas d'utilisation avancés, tels que l'extraction des numéros de version pour les packages logiciels.

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 '^[/]\|^~/'

Tous les cas d'utilisation utilisent le chemin complet d'origine en entrée, sans dépendre de résultats intermédiaires.

21
henfiber

La solution la plus simple et la plus simple (sur une seule ligne) est la suivante:

$ file=/blaabla/bla/blah/foo.txt
echo $(basename ${file%.*}) # foo
16
Ron

Le réponse acceptée fonctionne bien dans cas typiques , mais échoue dans Edge cas, à savoir:

  • Pour les noms de fichier sans extension (appelé suffixe dans le reste de cette réponse), extension=${filename##*.} renvoie le nom du fichier en entrée plutôt qu'une chaîne vide.
  • extension=${filename##*.} n'inclut pas le . initial, contrairement à la convention.
    • . ne fonctionne pas pour les noms de fichiers sans suffixe.
  • filename="${filename%.*}" sera la chaîne vide, si le nom du fichier en entrée commence par . et ne contient plus aucun caractère . (par exemple, .bash_profile), contrairement à la convention.

---------

Ainsi, la complexité d'une solution robuste couvrant tous les cas Edge appelle une fonction - voir sa définition ci-dessous; il peut retourner tous les composants d’un chemin].

Exemple d'appel:

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

Notez que les arguments après le chemin d’entrée sont choisis librement, variable de position , noms .
Pour ignorer les variables sans intérêt précédant celles qui le sont, spécifiez _ (pour utiliser la variable jetable $_) ou ''; Par exemple, pour extraire le nom de fichier racine et son extension uniquement, utilisez 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

Code de test qui exerce la fonction:

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

Résultats attendus - notez les cas Edge:

  • un nom de fichier sans suffixe
  • un nom de fichier commençant par . ( et non considéré comme le début du suffixe)
  • un chemin d'entrée se terminant par / (le / suivant est ignoré)
  • un chemin d'entrée qui est un nom de fichier uniquement (. est renvoyé en tant que chemin parent)
  • un nom de fichier qui a plus de .- jeton préfixé (seul le dernier est considéré comme le suffixe):
----- /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
16
mklement0

Je pense que si vous avez juste besoin du nom du fichier, vous pouvez essayer ceci:

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"

Et c'est tout = D.

12
Andrew Woolfgang

Vous pouvez forcer la coupe pour afficher tous les champs et les suivants en ajoutant - au numéro de champ.

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

Donc si FILE est eth0.pcap.gz, EXTENSION sera pcap.gz

En utilisant la même logique, vous pouvez également extraire le nom du fichier en utilisant '-' avec couper comme suit:

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

Cela fonctionne même pour les noms de fichiers qui n'ont aucune extension.

10
maciek gajewski

Ok, donc si je comprends bien, le problème est de savoir comment obtenir le nom et l’extension complète d’un fichier comportant plusieurs extensions, par exemple stuff.tar.gz.

Cela fonctionne pour moi:

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

Cela vous donnera stuff comme nom de fichier et .tar.gz comme extension. Cela fonctionne pour n'importe quel nombre d'extensions, y compris 0. J'espère que cela aidera pour tous ceux qui ont le même problème =)

7
Al3xXx

Reconnaissance de fichier magique

En plus des nombreuses bonnes réponses à cette question de débordement de pile, j'aimerais ajouter:

Sous Linux et d'autres Unix, il existe une commande magique nommée file qui détecte le type de fichier en analysant certains premiers octets du fichier. C'est un très vieil outil, initialement utilisé pour les serveurs d'impression (s'il n'a pas été créé pour ... je n'en suis pas sûr).

file myfile.txt
myfile.txt: UTF-8 Unicode text

file -b --mime-type myfile.txt
text/plain

Des extensions de standards peuvent être trouvées dans /etc/mime.types (sur mon Debian Bureau GNU/Linux. Voir man file et man mime.types. Peut-être devez-vous installer le file Utility et mime-support packages):

grep $( file -b --mime-type myfile.txt ) </etc/mime.types
text/plain      asc txt text pot brf srt

Vous pouvez créer une fonction bash pour déterminer la bonne extension. Il y a un petit échantillon (pas parfait):

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 </etc/mime.types
    case ${_basemimetype##*[/.-]} in
        executable ) _basemimetype='' ;;
        shellscript ) _basemimetype='sh' ;;
        dosexec ) _basemimetype='exe' ;;
        * ) ;;
    esac
    [ "$_mimetype" ] && [ "$_basemimetype" != "$_mimetype" ] &&
      printf ${2+-v} $2 "%s.%s" ${_basemimetype##*[/.-]} ${_mimetype##*[/.-]} ||
      printf ${2+-v} $2 "%s" ${_basemimetype##*[/.-]}
}

Cette fonction pourrait définir une variable Bash utilisable ultérieurement:

(Ceci est inspiré de la bonne réponse de @Petesh):

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

echo "$fullfile -> $filename . $extension"
7
F. Hauri

J'utilise le script suivant

$ echo "foo.tar.gz"|rev|cut -d"." -f3-|rev
foo
6
Joydip Datta
$ F = "text file.test.txt"  
$ echo ${F/*./}  
txt  

Ceci concerne plusieurs points et espaces dans un nom de fichier. Toutefois, s'il n'y a pas d'extension, il retourne le nom de fichier lui-même. Facile à vérifier cependant; il suffit de tester si le nom de fichier et l'extension sont identiques.

Naturellement, cette méthode ne fonctionne pas pour les fichiers .tar.gz. Cependant, cela pourrait être traité en deux étapes. Si l'extension est gz, vérifiez à nouveau s'il existe également une extension tar.

5
Miriam English

Comment extraire le nom de fichier et l'extension 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

Mises en garde: Divise le dernier point, ce qui fonctionne bien pour les noms de fichiers contenant des points, mais pas pour les extensions comportant des points. Voir exemple ci-dessous.

Utilisation:

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

Il y a probablement de meilleures façons de le faire. N'hésitez pas à éditer ma réponse pour l'améliorer.


Si vous avez affaire à un nombre limité d'extensions et que vous les connaissez toutes, essayez ceci:

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

Ceci ( ne contient pas comme premier exemple, mais vous devez gérer tous les cas, ce qui pourrait être plus fastidieux en fonction du nombre d'extensions que vous pouvez attendre.

4
Dennis

Voici le code avec AWK . Cela peut être fait plus simplement. Mais je ne suis pas bon dans AWK.

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

Construire à partir de Petesh , si seul le nom de fichier est requis, les chemins et les extensions peuvent être supprimés sur une seule ligne,

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

Utilisez simplement ${parameter%Word}

Dans ton cas:

${FILE%.*}

Si vous voulez le tester, tout le travail suivant, et simplement supprimer l'extension:

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

Basé en grande partie sur l'excellent réseau de @ mklement0, et regorgeant de hasard, utile bashismes - ainsi que d'autres réponses à cette/d'autres questions/"ce foutu Internet" ... J'ai tout emballé un peu, un peu plus compréhensible, réutilisable fonction pour mon (ou votre) .bash_profile qui prend soin de ce que (je considère) devrait être une version plus robuste de dirname/basename/qu'avez-vous ..

function path { SAVEIFS=$IFS; IFS=""   # stash IFS for safe-keeping, etc.
    [[ $# != 2 ]] && echo "usage: path <path> <dir|name|fullname|ext>" && 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 extensionless 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
}     

Exemples d'utilisation ...

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 <path> <dir|name|fullname|ext>
2
Alex Gray

Si vous souhaitez également autoriser les extensions vide, voici la plus courte des réponses:

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

1ère ligne expliquée: Il correspond à PATH.EXT ou à ANYTHING et le remplace par EXT. Si ANYTHING était apparié, le groupe ext n'est pas capturé.

2
Blauhirn

Une réponse simple:

Pour développer la variable POSIX réponse , notez que vous pouvez créer des motifs plus intéressants. Donc, pour le cas détaillé ici, vous pouvez simplement faire ceci:

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

Cela coupera la dernière occurrence de .tar. <quelque chose> .

Plus généralement, si vous souhaitez supprimer la dernière occurrence de. <quelque chose> . <quelque chose-d'autre> alors

${1.*.*}

devrait bien fonctionner.

Le lien la réponse ci-dessus semble être mort. Voici une excellente explication de la manipulation de chaîne que vous pouvez faire directement dans Bash, à partir de TLDP .

2
RandyP

C'est le seul qui a fonctionné pour moi:

path='folder/other_folder/file.js'

base=${path##*/}
echo ${base%.*}

>> file

Cela peut également être utilisé en interpolation de chaîne, mais vous devez malheureusement définir au préalable base.

1
Ken Mueller

Parmi les réponses ci-dessus, le plus court oneliner à imiter Python

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

présumer que votre fichier a vraiment une extension, est

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

IMHO la meilleure solution a déjà été donnée (en utilisant l'expansion des paramètres de Shell) et sont les mieux notés pour le moment.

J'ajoute cependant celui-ci qui utilise simplement des commandes dumbs, ce qui n'est pas efficace et que personne de grave ne devrait utiliser jamais:

FILENAME=$(echo $FILE | cut -d . -f 1-$(printf $FILE | tr . '\n' | wc -l))
EXTENSION=$(echo $FILE | tr . '\n' | tail -1)

Ajouté juste pour le plaisir :-)

1
Bruno BEAUFILS

En utilisant le fichier d'exemple /Users/Jonathan/Scripts/bash/MyScript.sh, ce code:

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

${ME} sera MyScript et ${MY_EXT} sera .sh:


Scénario:

#!/bin/bash
set -e

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

echo "${ME} - ${MY_EXT}"

Quelques 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
1
chown

Peut-être qu'il y a une option dans tar pour le faire; avez-vous vérifié l'homme? Sinon, vous pouvez utiliser extension de chaîne Bash :

test="mpc-1.0.1.tar.gz"
noExt="${test/.tar.gz/}" # Remove the string '.tar.gz'
echo $noExt
0
Édouard Lopez

Afin de rendre dir plus utile (dans le cas où un fichier local sans chemin est spécifié en entrée), j’ai fait ce qui suit:

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

Cela vous permet de faire quelque chose d’utile comme ajouter un suffixe au nom du fichier d’entrée en tant que:

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"
0
Bill Gale

Voici l'algorithme que j'ai utilisé pour trouver le nom et l'extension d'un fichier lorsque j'ai écrit un script Bash afin de rendre les noms uniques lorsque les noms sont en conflit par rapport à la casse.

#! /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 

Le test effectué.

$ 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: Le programme complet de translittération et d’autres tests sont disponibles ici: https://www.dropbox.com/s/4c6m0f2e28a1vxf/avoid-clashes-code.zip?dl=

0
historystamp

Vous pouvez utiliser

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

pour obtenir le nom du fichier et

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

pour obtenir une extension.

Cas de test:

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
0
Vytenis Bivainis

Voici une solution sed qui extrait les composants de chemin sous différentes formes et peut gérer la plupart des cas Edge:

_## Enter the input path and field separator character, for example:
## (separatorChar must not be present in inputPath)

inputPath="/path/to/Foo.bar"
separatorChar=":"

## sed extracts the path components and assigns them to output variables

oldIFS="$IFS"
IFS="$separatorChar"
read dirPathWithSlash dirPath fileNameWithExt fileName fileExtWithDot fileExt <<<"$(sed -En '
s/^[[:space:]]+//
s/[[:space:]]+$//
t l1
:l1
s/^([^/]|$)//
t
s/[/]+$//
t l2
:l2
s/^$/filesystem\/\
filesystem/p
t
h
s/^(.*)([/])([^/]+)$/\1\2\
\1\
\3/p
g
t l3
:l3
s/^.*[/]([^/]+)([.])([a-zA-Z0-9]+)$/\1\
\2\3\
\3/p
t
s/^.*[/](.+)$/\1/p
' <<<"$inputPath" | tr "\n" "$separatorChar")"
IFS="$oldIFS"

## Results (all use separatorChar=":")

## inputPath        = /path/to/Foo.bar
## dirPathWithSlash = /path/to/
## dirPath          = /path/to 
## fileNameWithExt  = Foo.bar
## fileName         = Foo
## fileExtWithDot   = .bar
## fileExt          = bar

## inputPath        = /path/to/Foobar
## dirPathWithSlash = /path/to/
## dirPath          = /path/to 
## fileNameWithExt  = Foobar
## fileName         = Foobar
## fileExtWithDot   =
## fileExt          =

## inputPath        = /path/to/...bar
## dirPathWithSlash = /path/to/
## dirPath          = /path/to 
## fileNameWithExt  = ...bar
## fileName         = ..
## fileExtWithDot   = .bar
## fileExt          = bar

## inputPath        = /path/to/..bar
## dirPathWithSlash = /path/to/
## dirPath          = /path/to 
## fileNameWithExt  = ..bar
## fileName         = .
## fileExtWithDot   = .bar
## fileExt          = bar

## inputPath        = /path/to/.bar
## dirPathWithSlash = /path/to/
## dirPath          = /path/to 
## fileNameWithExt  = .bar
## fileName         = .bar
## fileExtWithDot   = 
## fileExt          = 

## inputPath        = /path/to/...
## dirPathWithSlash = /path/to/
## dirPath          = /path/to 
## fileNameWithExt  = ...
## fileName         = ...
## fileExtWithDot   =
## fileExt          =

## inputPath        = /path/to/Foo.
## dirPathWithSlash = /path/to/
## dirPath          = /path/to 
## fileNameWithExt  = Foo.
## fileName         = Foo.
## fileExtWithDot   =
## fileExt          =

## inputPath        = / (the root directory)
## dirPathWithSlash = filesystem/
## dirPath          = filesystem
## fileNameWithExt  =
## fileName         =
## fileExtWithDot   =
## fileExt          =

## inputPath        =  (invalid because empty)
## dirPathWithSlash =
## dirPath          =
## fileNameWithExt  =
## fileName         =
## fileExtWithDot   =
## fileExt          =

## inputPath        = Foo/bar (invalid because doesn't start with a forward slash)
## dirPathWithSlash =
## dirPath          =
## fileNameWithExt  =
## fileName         =
## fileExtWithDot   =
## fileExt          =
_

Voilà comment cela fonctionne:

sed analyse le chemin d'entrée et imprime les composants de chemin suivants dans l'ordre, sur des lignes distinctes:

  • chemin de répertoire avec une barre oblique
  • chemin de répertoire sans barre oblique
  • nom de fichier avec extension
  • nom de fichier sans extension
  • extension de fichier avec un caractère de point
  • extension de fichier sans caractère de point

tr convertit la sortie sed en une chaîne de séparation délimitée par des caractères des composants de chemin ci-dessus.

read utilise le caractère de séparation comme séparateur de champ (_IFS="$separatorChar"_) et affecte chacun des composants de chemin à sa variable respective.

Voici comment fonctionne la construction sed:

  • s/^[[:space:]]+// et s/[[:space:]]+$// supprime les espaces blancs de début et/ou de fin
  • t l1 et :l1 actualise la fonction t pour la prochaine fonction s
  • s/^([^/]|$)// et t recherche un chemin d'entrée non valide (qui ne commence pas par une barre oblique), auquel cas il laisse toutes les lignes de sortie sont vides et ferme la commande sed
  • s/[/]+$// supprime les barres obliques finales
  • t l2 et :l2 actualise la fonction t pour la prochaine fonction s
  • s/^$/filesystem\/\\[newline]filesystem/p et t teste le cas particulier où le chemin d'entrée est constitué du répertoire racine /, auquel cas il imprime le système de fichiers/ et le système de fichiers pour le dirPathWithSlash et dirPath lignes de sortie, laisse toutes les autres lignes de sortie vides et quitte le sed commande
  • h enregistre le chemin d'entrée dans l'espace d'attente
  • s/^(.*)([/])([^/]+)$/\1\2\\[newline]\1\\[newline]\3/p affiche le dirPathWithSlash , dirPath et fileNameWithExt lignes de sortie
  • g récupère le chemin d'entrée dans l'espace d'attente
  • t l3 et :l3 actualise la fonction t pour la prochaine fonction s
  • s/^.*\[/]([^/]+)([.])([a-zA-Z0-9]+)$/\1\\[newline]\2\3\\[newline]\3/p et t imprime le nom_fichier , fileExtWithDot , et fileExt lignes de sortie pour le cas où une extension de fichier existe (supposée être composée uniquement de caractères alphanumériques), puis quitte la commande sed
  • s/^.*\[/](.+)$/\1/p imprime le nom_fichier mais pas le fileExtWithDot , et fileExt lignes de sortie pour le cas où une extension de fichier n'existe pas, puis quitte la commande sed.
0
scolfax