Comment puis-je supprimer l'extension d'un nom de fichier dans un script shell?

147

Quel est le problème avec le code suivant?

name='$filename | cut -f1 -d'.''

Tel quel, j'obtiens la chaîne littérale $filename | cut -f1 -d'.', mais si je supprime les guillemets, je n'obtiens rien. Pendant ce temps, en tapant

"test.exe" | cut -f1 -d'.'

dans une coquille me donne la sortie que je veux, test. Je sais déjà qu'on lui $filenamea attribué la bonne valeur. Ce que je veux faire, c'est attribuer à une variable le nom de fichier sans l'extension.

mimicocotope
la source
6
basename $filename .exeferait la même chose. Cela suppose que vous sachiez toujours quelle extension vous souhaitez supprimer.
mpe
7
@mpe, tu veux dire basename "$filename" .exe. Sinon, les noms de fichiers avec des espaces seraient de mauvaises nouvelles.
Charles Duffy

Réponses:

114

Vous devez utiliser la syntaxe de substitution de commande$(command) lorsque vous souhaitez exécuter une commande dans script / commande.

Donc votre ligne serait

name=$(echo "$filename" | cut -f 1 -d '.')

Explication du code:

  1. echoobtenir la valeur de la variable $filenameet l'envoyer à la sortie standard
  2. Nous récupérons ensuite la sortie et la dirigons vers la cutcommande
  3. Le cututilisera le. comme délimiteur (également connu sous le nom de séparateur) pour couper la chaîne en segments et en -fsélectionnant le segment que nous voulons avoir en sortie
  4. Ensuite, la $()substitution de commande obtiendra la sortie et retournera sa valeur
  5. La valeur retournée sera affectée à la variable nommée name

Notez que cela donne la partie de la variable jusqu'à la première période .:

$ filename=hello.world
$ echo "$filename" | cut -f 1 -d '.'
hello
$ filename=hello.hello.hello
$ echo "$filename" | cut -f 1 -d '.'
hello
$ filename=hello
$ echo "$filename" | cut -f 1 -d '.'
hello
Rohan
la source
Merci. J'ai également remarqué que je devais utiliser la commande echo. name =echo $filename | cut -f1 -d'.'
mimicocotopus
17
Les backticks sont obsolètes par POSIX, $()c'est préférable.
jordanm
3
La bifurcation et la tuyauterie pour atteindre quelques caractères est la pire solution imaginable.
Jens
39
Le problème avec cette réponse est qu'elle suppose que la chaîne d'entrée n'a qu'un seul point ... @chepner ci-dessous a une bien meilleure solution ... name = $ {filename%. *}
Scott Stensland
4
Cette réponse est caractéristique des débutants et ne doit pas être répandue. Utilisez le mécanisme intégré tel que décrit par la réponse de
Chepner
261

Vous pouvez également utiliser l'expansion des paramètres:

$ filename=foo.txt
$ echo "${filename%.*}"
foo
chepner
la source
6
ici l'explication de la commande: gnu.org/software/bash/manual/html_node
Carlos Robles
1
Et là, j'étais sur le point de l'utiliser echo -n "This.File.Has.Periods.In.It.txt" | awk -F. '{$NF=""; print $0}' | tr ' ' '.' | rev | cut -c 2- | rev. Merci.
user208145
1
Cela fonctionne-t-il avec des fichiers avec plusieurs extensions comme image.png.gz?
Hawker65
11
%.*supprimera uniquement la dernière extension; si vous souhaitez supprimer toutes les extensions, utilisez %%.*.
chepner
1
Cela semble bien mieux fonctionner que la réponse acceptée. Il ne supprime la dernière extension que si le fichier a plus d'un point, tandis que cut supprime tout après le premier point.
Dale Anderson
117

Si vous connaissez l'extension, vous pouvez utiliser basename

$ basename /home/jsmith/base.wiki .wiki
base
Steven Penny
la source
Comment puis-je supprimer juste .wikiet finir avec /home/jsmith/base?
Gabriel Staples
Mise à jour: réponse: voir stackoverflow.com/a/32584935/4561887 . Fonctionne parfaitement!
Gabriel Staples
20

Si votre nom de fichier contient un point (autre que celui de l'extension), utilisez ceci:

echo $filename | rev | cut -f 2- -d '.' | rev
manish_s
la source
1
J'ai oublié le régime du milieu, mais une fois que je l'ai vu, c'était super!
suprême Pooba
C'est encore mieux avec une -soption donnée à cut, de sorte qu'elle renvoie une chaîne vide chaque fois que le nom de fichier ne contient pas de point.
Hibou57
1
Cela devrait être la réponse acceptée à mon avis, car cela fonctionne sur un chemin avec des points, avec des fichiers cachés commençant par un point, ou même avec un fichier avec plusieurs extensions.
Tim Krief le
20
file1=/tmp/main.one.two.sh
t=$(basename "$file1")                        # output is main.one.two.sh
name=$(echo "$file1" | sed -e 's/\.[^.]*$//') # output is /tmp/main.one.two
name=$(echo "$t" | sed -e 's/\.[^.]*$//')     # output is main.one.two

utilisez ce que vous voulez. Ici, je suppose que le dernier .(point) suivi du texte est l'extension.

Raghwendra
la source
6
#!/bin/bash
file=/tmp/foo.bar.gz
echo $file ${file%.*}

les sorties:

/tmp/foo.bar.gz /tmp/foo.bar

Notez que seule la dernière extension est supprimée.

C. Paul Bond
la source
3

Ma recommandation est d'utiliser basename.
C'est par défaut dans Ubuntu, un code visuellement simple et traiter la majorité des cas.

Voici quelques sous-cas pour traiter les espaces et les multi-points / sous-extensions:

pathfile="../space fld/space -file.tar.gz"
echo ${pathfile//+(*\/|.*)}

Il se débarrasse généralement de l'extension du premier ., mais échoue dans notre ..chemin

echo **"$(basename "${pathfile%.*}")"**  
space -file.tar     # I believe we needed exatly that

Voici une note importante:

J'ai utilisé des guillemets doubles à l'intérieur des guillemets doubles pour gérer les espaces. Le devis simple ne passera pas en raison de l'envoi du $. Bash est inhabituel et lit les «deuxièmes» premières «citations» en raison de l'expansion.

Cependant, vous devez toujours penser à .hidden_files

hidden="~/.bashrc"
echo "$(basename "${hidden%.*}")"  # will produce "~" !!!  

pas le résultat attendu. Pour y arriver, utilisez $HOMEou /home/user_path/
car encore une fois bash est "inhabituel" et ne développez pas "~" (recherchez bash BashPitfalls)

hidden2="$HOME/.bashrc" ;  echo '$(basename "${pathfile%.*}")'
Alexey K.
la source
1
#!/bin/bash
filename=program.c
name=$(basename "$filename" .c)
echo "$name"

les sorties:

program
booboo
la source
En quoi est-ce différent de la réponse donnée par Steven Penny il y a 3 ans?
gniourf_gniourf
1

Comme le souligne Hawker65 dans le commentaire de la réponse de chepner, la solution la plus votée ne prend pas en charge les extensions multiples (comme filename.tar.gz), ni les points dans le reste du chemin (comme this.path / with .dots / in.path.name). Une solution possible est:

a=this.path/with.dots/in.path.name/filename.tar.gz
echo $(dirname $a)/$(basename $a | cut -d. -f1)
MadMage
la source
Celui-ci supprime "tar.gz" en sélectionnant des caractères avant la première instance d'un point dans le nom de fichier sans compter le chemin. On ne veut probablement pas supprimer les extensions de cette façon.
Frotz
0

Deux problèmes avec votre code:

  1. Vous avez utilisé un '(coche) au lieu d'un' (coche arrière) pour entourer les commandes qui génèrent la chaîne que vous souhaitez stocker dans la variable.
  2. Vous n'avez pas "renvoyé" la variable "$ filename" au tube dans la commande "cut".

Je changerais votre code en "name =` echo $ filename | cut -f 1 -d '.' `", comme indiqué ci-dessous (encore une fois, notez les coches arrière entourant la définition de la variable de nom):

$> filename=foo.txt
$> echo $filename
foo.txt
$> name=`echo $filename | cut -f1 -d'.'`
$> echo $name
foo
$> 
FanDeLaU
la source