Come rimuovo il suffisso del file e la porzione di percorso da una stringa di percorso in Bash?


396

Dato un percorso del file stringa come /foo/fizzbuzz.bar, come dovrei usare bash per estrarre solo la fizzbuzzparte di detta stringa?


Informazioni che puoi trovare nel manuale di Bash , cercare ${parameter%word}e ${parameter%%word}nella sezione di corrispondenza delle parti finali.
agosto

Risposte:


574

Ecco come farlo con gli operatori # e% in Bash.

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

${x%.bar}potrebbe anche essere ${x%.*}quello di rimuovere tutto dopo un punto o ${x%%.*}di rimuovere tutto dopo il primo punto.

Esempio:

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

La documentazione è disponibile nel manuale di Bash . Cerca ${parameter%word}e ${parameter%%word}trascina la sezione di corrispondenza delle parti.


Ho finito per usare questo perché era il più flessibile e c'erano un paio di altre cose simili che volevo fare e che ha fatto bene.
Lawrence Johnston,

Questa è probabilmente la più flessibile di tutte le risposte pubblicate, ma penso che anche le risposte che suggeriscono i comandi basename e dirname meritino qualche attenzione. Possono essere solo il trucco se non hai bisogno di altri abbinamenti di fantasia.
mgadda,

7
Come si chiama $ {x% .bar}? Vorrei saperne di più.
Basilio

14
@Basil: espansione dei parametri. Su una console digitare "man bash" e quindi digitare "/ parametro extension"
Zan Lynx l'

1
Immagino che la spiegazione di "man bash" abbia senso se sai già cosa fa o se l'hai provato tu stesso nel modo più duro. È quasi cattivo come riferimento git. Vorrei solo google invece.
triplebig

227

guarda il comando basename:

NAME="$(basename /foo/fizzbuzz.bar .bar)"

11
Probabilmente la più semplice di tutte le soluzioni attualmente offerte ... anche se userei $ (...) invece di backtick.
Michael Johnson,

6
Più semplice ma aggiunge una dipendenza (non enorme o strana, lo ammetto). Deve anche conoscere il suffisso.
Vinko Vrsalovic,

E può essere usato per rimuovere qualsiasi cosa dalla fine, fondamentalmente fa solo una rimozione della stringa dalla fine.
Smar,

2
Il problema è il colpo di tempo. Ho appena cercato la domanda per questa discussione dopo aver visto bash impiegare quasi 5 minuti per elaborare 800 file, usando basename. Usando il metodo regex sopra, il tempo è stato ridotto a circa 7 secondi. Sebbene questa risposta sia più facile da eseguire per il programmatore, il tempo trascorso è semplicemente troppo. Immagina una cartella con un paio di migliaia di file! Ho alcune di queste cartelle.
xizdaqrian,

@xizdaqrian Questo è assolutamente falso. Questo è un programma semplice, che non dovrebbe richiedere mezzo secondo per tornare. Ho appena eseguito time find / home / me / dev -name "* .py" .py -exec basename {} \; e ha rimosso l'estensione e la directory per 1500 file in 1 secondo in totale.
Laszlo Treszkai,

40

Bash puro, fatto in due operazioni separate:

  1. Rimuovere il percorso da una stringa di percorso:

    path=/foo/bar/bim/baz/file.gif
    
    file=${path##*/}  
    #$file is now 'file.gif'
    
  2. Rimuovere l'estensione da una stringa di percorso:

    base=${file%.*}
    #${base} is now 'file'.
    

18

Usando basename ho usato quanto segue per raggiungere questo obiettivo:

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

    # Do things with $fname
done;

Questo non richiede alcuna conoscenza a priori dell'estensione del file e funziona anche quando hai un nome file che ha punti nel suo nome file (davanti alla sua estensione); richiede però il programma basename, ma fa parte dei coreutils GNU, quindi dovrebbe essere fornito con qualsiasi distribuzione.


1
Risposta eccellente! rimuove l'estensione in modo molto pulito, ma non rimuove. alla fine del nome file.
metrix,

3
@metrix basta aggiungere il "." prima di $ ext, ovvero: fname=`basename $file .$ext`
Carlos Troncoso,

13

Modo puro bash:

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

Questa funzionalità è spiegata da man bash in "Parameter Expansion". I modi non bash abbondano: awk, perl, sed e così via.

EDIT: funziona con punti nei suffissi dei file e non è necessario conoscere il suffisso (estensione), ma non funziona con i punti nel nome stesso.


11

Le funzioni basename e dirname sono ciò che cerchi:

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

Ha prodotto:

basename: fizzbuzz.bar
basename + remove .bar: fizzbuzz
dirname: /foo

1
Sarebbe utile correggere il preventivo qui - magari eseguirlo attraverso shellcheck.netmystring=$1 anziché con il valore costante corrente (che sopprimerà diversi avvisi, essendo certo di non contenere spazi / caratteri glob / etc), e risolvendo i problemi che esso reperti?
Charles Duffy,

Bene, ho apportato alcune modifiche appropriate per supportare le virgolette in $ mystring. Accidenti, è stato tanto tempo fa che l'ho scritto :)
Jerub,

Sarebbe un ulteriore miglioramento per citare i risultati: echo "basename: $(basename "$mystring")"- in questo modo se mystring='/foo/*'non si ottiene il *sostituito con un elenco di file nella directory corrente al basename termine.
Charles Duffy,

6

L'utilizzo basenamepresuppone che tu sappia qual è l'estensione del file, non è vero?

E credo che i vari suggerimenti di espressioni regolari non affrontino un nome di file contenente più di un "."

Quanto segue sembra far fronte a punti doppi. Oh, e nomi di file che contengono un "/" se stessi (solo per calci)

Per parafrasare Pascal, "Mi dispiace che questo script sia così lungo. Non ho avuto il tempo di accorciarlo"


  #!/usr/bin/perl
  $fullname = $ARGV[0];
  ($path,$name) = $fullname =~ /^(.*[^\\]\/)*(.*)$/;
  ($basename,$extension) = $name =~ /^(.*)(\.[^.]*)$/;
  print $basename . "\n";
 

1
Questo è bello e robusto
Gaurav Jain, il

4

Oltre alla sintassi conforme POSIX utilizzata in questa risposta ,

basename string [suffix]

come in

basename /foo/fizzbuzz.bar .bar

GNUbasename supporta un'altra sintassi:

basename -s .bar /foo/fizzbuzz.bar

con lo stesso risultato. La differenza e il vantaggio è che -simplica -a, che supporta più argomenti:

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

Questo può anche essere reso sicuro dal nome del file separando l'output con byte NUL usando l' -zopzione, ad esempio per questi file che contengono spazi, righe e caratteri glob (citati da ls):

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

Lettura in un array:

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

readarray -drichiede Bash 4.4 o più recente. Per le versioni precedenti, dobbiamo eseguire il loop:

while IFS= read -r -d '' fname; do arr+=("$fname"); done < <(basename -zs .bar has*)

Inoltre, il suffisso specificato viene rimosso nell'output se presente (e ignorato diversamente).
aksh1618,


3

Se non puoi usare il nome di base come suggerito in altri post, puoi sempre usare sed. Ecco un (brutto) esempio. Non è il massimo, ma funziona estraendo la stringa desiderata e sostituendo l'input con la stringa desiderata.

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

Che ti darà l'output

FizzBuzz


Sebbene questa sia la risposta alla domanda originale, questo comando è utile quando ho linee di percorsi in un file per estrarre i nomi di base per stamparli sullo schermo.
Sangcheol Choi,

2

Attenzione alla soluzione perl suggerita: rimuove qualsiasi cosa dopo il primo punto.

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

Se vuoi farlo con perl, questo funziona:

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

Ma se stai usando Bash, le soluzioni con y=${x%.*}(o basename "$x" .extse conosci l'estensione) sono molto più semplici.


1

Il basename lo fa, rimuove il percorso. Rimuoverà anche il suffisso se fornito e se corrisponde al suffisso del file, ma è necessario conoscere il suffisso da dare al comando. Altrimenti puoi usare mv e capire quale dovrebbe essere il nuovo nome in qualche altro modo.


1

Combinazione della risposta più votata con la seconda risposta più votata per ottenere il nome file senza il percorso completo:

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

Perché stai usando un array qui? Inoltre, perché usare basename?
codeforester
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.