Comment supprimer le suffixe de fichier et la partie chemin d'une chaîne de chemin dans Bash?

396

Étant donné un chemin de fichier de chaîne tel que /foo/fizzbuzz.bar, comment utiliserais-je bash pour extraire uniquement la fizzbuzzpartie de ladite chaîne?

Lawrence Johnston
la source
Informations que vous pouvez trouver dans le manuel de Bash , recherchez ${parameter%word}et ${parameter%%word}dans la section de correspondance des portions de fin.
1ac0

Réponses:

574

Voici comment le faire avec les opérateurs # et% dans Bash.

$ x="/foo/fizzbuzz.bar"
$ y=${x%.bar}
$ echo ${y##*/}
fizzbuzz

${x%.bar}pourrait également être ${x%.*}de tout supprimer après un point ou ${x%%.*}de tout supprimer après le premier point.

Exemple:

$ x="/foo/fizzbuzz.bar.quux"
$ y=${x%.*}
$ echo $y
/foo/fizzbuzz.bar
$ y=${x%%.*}
$ echo $y
/foo/fizzbuzz

La documentation se trouve dans le manuel de Bash . Rechercher ${parameter%word}et ${parameter%%word}suivre la section de correspondance des portions.

Zan Lynx
la source
J'ai fini par utiliser celui-ci parce qu'il était le plus flexible et il y avait quelques autres choses similaires que je voulais faire aussi bien que cela a bien fonctionné.
Lawrence Johnston
C'est probablement la plus flexible de toutes les réponses publiées, mais je pense que les réponses suggérant les commandes basename et dirname méritent également une certaine attention. Ils peuvent être juste l'astuce si vous n'avez besoin d'aucune autre correspondance de motifs fantaisie.
mgadda
7
Comment s'appelle $ {x% .bar}? J'aimerais en savoir plus.
Basil
14
@Basil: Expansion des paramètres. Sur une console, tapez "man bash" puis tapez "/ paramètre expansion"
Zan Lynx
1
Je suppose que l'explication «man bash» a du sens si vous savez déjà ce qu'elle fait ou si vous l'avez essayé vous-même à la dure. C'est presque aussi mauvais que git reference. Je préfère le rechercher sur Google.
triplebig
227

regardez la commande basename:

NAME="$(basename /foo/fizzbuzz.bar .bar)"
zigdon
la source
11
Probablement la plus simple de toutes les solutions actuellement proposées ... bien que j'utiliserais $ (...) au lieu de backticks.
Michael Johnson,
6
Le plus simple mais ajoute une dépendance (pas énorme ou bizarre, je l'avoue). Il doit également connaître le suffixe.
Vinko Vrsalovic
Et peut être utilisé pour supprimer quoi que ce soit de la fin, en gros, il ne fait que retirer la chaîne de la fin.
Smar
2
Le problème est le temps frappé. Je viens de rechercher la question pour cette discussion après avoir regardé bash prendre près de 5 minutes pour traiter 800 fichiers, en utilisant basename. En utilisant la méthode d'expression régulière ci-dessus, le temps a été réduit à environ 7 secondes. Bien que cette réponse soit plus facile à réaliser pour le programmeur, le temps atteint est tout simplement trop. Imaginez un dossier contenant quelques milliers de fichiers! J'ai certains de ces dossiers.
xizdaqrian
@xizdaqrian C'est absolument faux. Il s'agit d'un programme simple, qui ne devrait pas prendre une demi-seconde pour revenir. Je viens d'exécuter time find / home / me / dev -name "* .py" .py -exec basename {} \; et il a supprimé l'extension et le répertoire de 1500 fichiers en 1 seconde au total.
Laszlo Treszkai
40

Pure bash, fait en deux opérations distinctes:

  1. Supprimez le chemin d'une chaîne de chemin:

    path=/foo/bar/bim/baz/file.gif
    
    file=${path##*/}  
    #$file is now 'file.gif'
  2. Supprimez l'extension d'une chaîne de chemin:

    base=${file%.*}
    #${base} is now 'file'.
Param
la source
18

En utilisant le nom de base, j'ai utilisé ce qui suit pour y parvenir:

for file in *; do
    ext=${file##*.}
    fname=`basename $file $ext`

    # Do things with $fname
done;

Cela ne nécessite aucune connaissance a priori de l'extension de fichier et fonctionne même lorsque vous avez un nom de fichier qui a des points dans son nom de fichier (devant son extension); il nécessite le programme basename, mais cela fait partie des coreutils GNU donc il devrait être livré avec n'importe quelle distribution.

Mike
la source
1
Excellente réponse! supprime l'extension d'une manière très propre, mais ne supprime pas le. à la fin du nom de fichier.
metrix
3
@metrix ajoutez simplement le "." avant $ ext, c'est-à-dire: fname=`basename $file .$ext`
Carlos Troncoso
13

Voie bash pure:

~$ x="/foo/bar/fizzbuzz.bar.quux.zoom"; 
~$ y=${x/\/*\//}; 
~$ echo ${y/.*/}; 
fizzbuzz

Cette fonctionnalité est expliquée dans man bash sous "Expansion des paramètres". Les moyens non bash abondent: awk, perl, sed et ainsi de suite.

EDIT: Fonctionne avec les points dans les suffixes de fichier et n'a pas besoin de connaître le suffixe (extension), mais ne fonctionne pas avec les points dans le nom lui-même.

Vinko Vrsalovic
la source
11

Les fonctions basename et dirname sont ce que vous recherchez:

mystring=/foo/fizzbuzz.bar
echo basename: $(basename "${mystring}")
echo basename + remove .bar: $(basename "${mystring}" .bar)
echo dirname: $(dirname "${mystring}")

A une sortie:

basename: fizzbuzz.bar
basename + remove .bar: fizzbuzz
dirname: /foo
Jerub
la source
1
Il serait utile de corriger la citation ici - peut-être exécutez-le via shellcheck.net avec mystring=$1plutôt que la valeur constante actuelle (qui supprimera plusieurs avertissements, étant certain de ne pas contenir d'espaces / caractères globaux / etc), et de résoudre les problèmes trouve?
Charles Duffy
Eh bien, j'ai apporté des modifications appropriées pour prendre en charge les guillemets dans $ mystring.
Mon Dieu
Serait une amélioration supplémentaire pour citer les résultats: echo "basename: $(basename "$mystring")"- de cette façon si mystring='/foo/*'vous n'obtenez pas le *remplacé par une liste de fichiers dans le répertoire en cours après la basename fin.
Charles Duffy
6

L'utilisation basenamesuppose que vous connaissez l'extension du fichier, n'est-ce pas?

Et je crois que les diverses suggestions d'expressions régulières ne font pas face à un nom de fichier contenant plus d'un "."

Ce qui suit semble faire face à des points doubles. Oh, et les noms de fichiers qui contiennent eux-mêmes un "/" (juste pour les coups de pied)

Pour paraphraser Pascal, "Désolé, ce script est si long. Je n'ai pas eu le temps de le raccourcir"


  #!/usr/bin/perl
  $fullname = $ARGV[0];
  ($path,$name) = $fullname =~ /^(.*[^\\]\/)*(.*)$/;
  ($basename,$extension) = $name =~ /^(.*)(\.[^.]*)$/;
  print $basename . "\n";
 
Andrew Edgecombe
la source
1
C'est sympa et robuste
Gaurav Jain
4

En plus de la syntaxe conforme POSIX utilisée dans cette réponse ,

basename string [suffix]

un péché

basename /foo/fizzbuzz.bar .bar

GNUbasename prend en charge une autre syntaxe:

basename -s .bar /foo/fizzbuzz.bar

avec le même résultat. La différence et l'avantage est que cela -simplique -a, qui prend en charge plusieurs arguments:

$ basename -s .bar /foo/fizzbuzz.bar /baz/foobar.bar
fizzbuzz
foobar

Cela peut même être rendu sûr pour le nom de fichier en séparant la sortie par des octets NUL en utilisant l' -zoption, par exemple pour ces fichiers contenant des blancs, des nouvelles lignes et des caractères globaux (cités par ls):

$ ls has*
'has'$'\n''newline.bar'  'has space.bar'  'has*.bar'

Lecture dans un tableau:

$ readarray -d $'\0' arr < <(basename -zs .bar has*)
$ declare -p arr
declare -a arr=([0]=$'has\nnewline' [1]="has space" [2]="has*")

readarray -dnécessite Bash 4.4 ou plus récent. Pour les anciennes versions, nous devons boucler:

while IFS= read -r -d '' fname; do arr+=("$fname"); done < <(basename -zs .bar has*)
Benjamin W.
la source
De plus, le suffixe spécifié est supprimé dans la sortie s'il est présent (et ignoré sinon).
aksh1618
3
perl -pe 's/\..*$//;s{^.*/}{}'
mopoke
la source
3

Si vous ne pouvez pas utiliser le nom de base comme suggéré dans d'autres articles, vous pouvez toujours utiliser sed. Voici un exemple (moche). Ce n'est pas le plus grand, mais cela fonctionne en extrayant la chaîne souhaitée et en remplaçant l'entrée par la chaîne souhaitée.

echo '/foo/fizzbuzz.bar' | sed 's|.*\/\([^\.]*\)\(\..*\)$|\1|g'

Qui vous donnera la sortie

fizzbuzz

nymacro
la source
Bien que ce soit la réponse à la question d'origine, cette commande est utile lorsque j'ai des lignes de chemins dans un fichier pour extraire les noms de base pour les imprimer à l'écran.
Sangcheol Choi
2

Méfiez-vous de la solution Perl suggérée: elle supprime tout après le premier point.

$ echo some.file.with.dots | perl -pe 's/\..*$//;s{^.*/}{}'
some

Si vous voulez le faire avec perl, cela fonctionne:

$ echo some.file.with.dots | perl -pe 's/(.*)\..*$/$1/;s{^.*/}{}'
some.file.with

Mais si vous utilisez Bash, les solutions avec y=${x%.*}(ou basename "$x" .extsi vous connaissez l'extension) sont beaucoup plus simples.

mivk
la source
1

Le nom de base fait cela, supprime le chemin d'accès. Il supprimera également le suffixe s'il est donné et s'il correspond au suffixe du fichier, mais vous devrez connaître le suffixe à attribuer à la commande. Sinon, vous pouvez utiliser mv et déterminer quel devrait être le nouveau nom d'une autre manière.

waynecolvin
la source
1

Combiner la réponse la mieux notée avec la deuxième réponse la mieux notée pour obtenir le nom de fichier sans le chemin complet:

$ x="/foo/fizzbuzz.bar.quux"
$ y=(`basename ${x%%.*}`)
$ echo $y
fizzbuzz
c.gutierrez
la source
Pourquoi utilisez-vous un tableau ici? Aussi, pourquoi utiliser le nom de base?
codeforester