Comment puis-je supprimer le suffixe de fichier et la portion de chemin d'une chaîne de chemin dans Bash?
Question
Étant donné un chemin de fichier de chaîne tel que /foo/fizzbuzz.bar
, comment pourrais-je utiliser bash pour extraire uniquement la partie fizzbuzz
de cette chaîne?
La solution
Voici comment procéder avec les opérateurs # et% de Bash.
$ x="/foo/fizzbuzz.bar"
$ y=${x%.bar}
$ echo ${y##*/}
fizzbuzz
$ {x% .bar}
peut également être $ {x%. *}
pour tout supprimer après un point ou $ {x %%. *}
pour 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 Manuel Bash . Recherchez les sections $ {paramètre% mot}
et $ {paramètre %% mot}
.
Autres conseils
regardez la commande basename:
NAME=$(basename /foo/fizzbuzz.bar .bar)
Pure bash, effectuée en deux opérations distinctes:
-
Supprimez le chemin d'une chaîne de chemin:
path=/foo/bar/bim/baz/file.gif file=${path##*/} #$file is now 'file.gif'
-
Supprimer l'extension d'un chemin d'accès:
base=${file%.*} #${base} is now 'file'.
À l'aide du nom de base, j'ai utilisé les éléments suivants pour y parvenir:
for file in *; do
ext=${file##*.}
fname=`basename $file $ext`
# Do things with $fname
done;
Cela ne nécessite aucune connaissance préalable de l’extension de fichier et fonctionne même lorsque vous avez un nom de fichier qui contient des points dans son nom de fichier (devant son extension); le programme basename
est cependant requis, mais cela fait partie de la coreutils de GNU et devrait donc être livré avec n’importe quelle distribution.
façon pure bash:
~$ x="/foo/bar/fizzbuzz.bar.quux.zoom";
~$ y=${x/\/*\//};
~$ echo ${y/.*/};
fizzbuzz
Cette fonctionnalité est expliquée dans man bash sous "Développement de paramètres". Les moyens non bash abondent: awk, perl, sed et ainsi de suite.
EDIT: Fonctionne avec des points dans le suffixe du fichier et n'a pas besoin de connaître le suffixe (extension), mais ne ne fonctionne pas avec des points . em> nommer lui-même.
Ce que vous recherchez, ce sont les fonctions basename et dirname:
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
perl -pe 's/\..*$//;s{^.*/}{}'
L'utilisation de basename
suppose que vous sachiez quelle est l'extension du fichier, n'est-ce pas?
Et je pense que les différentes suggestions d’expression rationnelle ne s’adaptent pas à un nom de fichier contenant plusieurs ".".
Ce qui suit semble faire face à des points doubles. Oh, et les noms de fichiers contenant un " / " eux-mêmes (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";
Si vous ne pouvez pas utiliser le nom de base comme suggéré dans d'autres publications, vous pouvez toujours utiliser sed. Voici un exemple (laid). Ce n'est pas ce qu'il y a de mieux, 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'
Ce qui vous donnera la sortie
fizzbuzz
En plus de la syntaxe conforme à POSIX utilisée dans cette réponse ,
basename string [suffix]
comme dans
basename /foo/fizzbuzz.bar .bar
GNU nom de base
supporte une autre syntaxe:
basename -s .bar /foo/fizzbuzz.bar
avec le même résultat. La différence et l'avantage est que -s
implique -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 avec des octets NUL en utilisant l'option -z
, par exemple pour ces fichiers contenant des blancs, des nouvelles lignes et des caractères globaux (cités par ls
):
$ ls has*
'has'
Lecture dans un tableau:
$ readarray -d
readarray -d
né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*)
\n''newline.bar' 'has space.bar' 'has*.bar'
Lecture dans un tableau:
<*>
readarray -d
nécessite Bash 4.4 ou plus récent. Pour les anciennes versions, nous devons boucler:
<*>\0' arr < <(basename -zs .bar has*)
$ declare -p arr
declare -a arr=([0]=
readarray -d
nécessite Bash 4.4 ou plus récent. Pour les anciennes versions, nous devons boucler:
<*>\n''newline.bar' 'has space.bar' 'has*.bar'
Lecture dans un tableau:
<*> readarray -d
nécessite Bash 4.4 ou plus récent. Pour les anciennes versions, nous devons boucler:
readarray -d
nécessite Bash 4.4 ou plus récent. Pour les anciennes versions, nous devons boucler:
Lecture dans un tableau:
<*> readarray -d
nécessite Bash 4.4 ou plus récent. Pour les anciennes versions, nous devons boucler:
Faites attention à la solution Perl suggérée: elle supprime tout ce qui se trouve 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 le nom de base "$ x" .ext
si vous connaissez la extension) sont beaucoup plus simples.
Le nom de base fait cela, supprime le chemin. Il supprimera également le suffixe s'il est indiqué et s'il correspond au suffixe du fichier, mais vous devez connaître le suffixe à attribuer à la commande. Sinon, vous pouvez utiliser mv et déterminer ce que le nouveau nom doit être autrement.
Combinant la réponse la mieux classée avec la réponse classée la deuxième mieux pour obtenir le nom de fichier sans le chemin complet:
$ x="/foo/fizzbuzz.bar.quux"
$ y=(`basename ${x%%.*}`)
$ echo $y
fizzbuzz