bash - suffixe - shell recuperer nom fichier sans extension




Extraire le nom de fichier et l'extension dans Bash (20)

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

La meilleure solution que j'ai trouvée jusqu'ici est:

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

C'est faux car cela ne fonctionne pas si le nom du fichier contient plusieurs "." personnages. Si, disons, j'ai abjs il considérera a et b.js , au lieu de ab et js .

Il peut être facilement fait en Python avec

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

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

De meilleures idées?


Reconnaissance de fichier magique

En plus du lot de bonnes réponses sur cette question , je voudrais ajouter:

Sous Linux et d'autres unixen, il y a une commande magique nommée file , qui fait la détection de type de fichier en analysant les premiers octets du fichier. C'est un outil très ancien, utilisé initialement pour les serveurs d'impression (s'il n'est pas créé pour ... je ne suis pas sûr de ça).

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

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

Les extensions de standards peuvent être trouvées dans /etc/mime.types (sur mon bureau Debian GNU / Linux, voir le man file man mime.types et man mime.types peut-être installer l'utilitaire de file et mime-support paquets mime-support ):

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 l'extension correcte. 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 qui peut être utilisée plus tard:

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

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

echo "$fullfile -> $filename . $extension"

Cela ne semble pas fonctionner si le fichier n'a pas d'extension, ou pas de nom de fichier. Voici ce que j'utilise; il utilise seulement des builtins et gère plus (mais pas tous) les noms de fichiers pathologiques.

#!/bin/bash
for fullpath in "[email protected]"
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/file /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 = "file"
    ext  = ""
/home/me/file.tar:
    dir  = "/home/me/"
    base = "file"
    ext  = "tar"
/home/me/file.tar.gz:
    dir  = "/home/me/"
    base = "file.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  = ""

Construire à partir de Petesh répondre, si seulement le nom de fichier est nécessaire, à la fois le chemin et l'extension peuvent être supprimés dans une seule ligne,

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

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

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.


La solution la plus petite et la plus simple (en ligne simple) est:

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

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

foo


Ok donc si je comprends bien, le problème ici est comment obtenir le nom et l'extension complète d'un fichier qui a plusieurs extensions, par exemple, stuff.tar.gz .

Cela fonctionne pour moi:

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

Cela vous donnera des stuff comme nom de fichier et .tar.gz comme extension. Cela fonctionne pour n'importe quel nombre d'extensions, y compris 0. Espérons que ceci aide pour n'importe qui ayant le même problème =)


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

Implémentation de référence

Documentation de os.path.splitext(path) :

Divisez le chemin du chemin en une paire (root, ext) telle que root + ext == path , et ext est vide ou commence par un point et contient au plus une période. Les périodes principales sur le nom de base sont ignorées. splitext('.cshrc') renvoie ('.cshrc', '') .

Code Python:

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

Mise en œuvre de Bash

Honorer les périodes de pointe

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

Ignorer les périodes principales

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

Tests

Voici des cas de test pour l'implémentation Ignorer les périodes principales , qui doit correspondre à l'implémentation de la référence Python à 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 passé.


Une réponse simple:

Pour développer la réponse aux variables POSIX , notez que vous pouvez faire des modèles plus intéressants. Donc, pour le cas détaillé ici, vous pouvez simplement faire ceci:

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

Cela va couper 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 de la réponse ci-dessus semble être mort. Voici une bonne explication d'un tas de manipulations de chaînes que vous pouvez faire directement dans Bash, à partir de TLDP .


Voici le code avec AWK . Cela peut être fait plus simplement. Mais je ne suis pas bon en 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

Vous pouvez forcer couper 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 , l'EXTENSION sera pcap.gz

En utilisant la même logique, vous pouvez aussi récupérer 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.


Vous pouvez utiliser

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

to get file name and

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

to get 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

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 l'a noté Clayton Hughes dans un commentaire, cela ne fonctionnera pas pour l'exemple réel 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 inconditionnellement les deux dernières extensions (alphanumériques).

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


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%%.*} Supprimerait avec gourmandise la correspondance la plus longue . et vous auriez la chaîne vide.

(Vous pouvez contourner cela 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

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

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"

$ F = "text file.test.txt"  
$ echo ${F/*./}  
txt  

Cela répond à plusieurs points et espaces dans un nom de fichier, mais s'il n'y a pas d'extension, il retourne le nom de fichier lui-même. Facile à vérifier cependant; juste tester pour le nom de fichier et l'extension étant la même chose.

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


pax> echo a.b.js | sed 's/\.[^.]*$//'
a.b
pax> echo a.b.js | sed 's/^.*\.//'
js

fonctionne très bien, donc vous pouvez 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 non "." caractères jusqu'à la fin de la ligne, avec rien (c'est-à-dire, il supprime tout de la finale "." à la fin de la ligne, inclusivement). C'est fondamentalement une substitution non-gourmande utilisant la ruse de regex.

La commande pour EXTENSION remplace un nombre quelconque de caractères suivi d'un "." caractère au début de la ligne, avec rien (c'est-à-dire, il supprime tout du début de la ligne au point final, inclusivement). C'est une substitution gourmande qui est l'action par défaut.





filenames