remove - script bash basename




Estrarre il nome file e l'estensione in Bash (20)

Voglio ottenere il nome file (senza estensione) e l'estensione separatamente.

La soluzione migliore che ho trovato finora è:

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

Questo è sbagliato perché non funziona se il nome del file contiene più . personaggi. Se, diciamo, ho abjs , prenderà in considerazione a e b.js , invece di ab e js .

Può essere fatto facilmente in Python con

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

ma preferirei non attivare un interprete Python solo per questo, se possibile.

Qualche idea migliore?


Riconoscimento di file magico

Oltre alle molte buone risposte su questa domanda di , vorrei aggiungere:

Sotto Linux e altri unixen, c'è un comando magico chiamato file , che esegue il rilevamento dei tipi di file analizzando alcuni primi byte di file. Questo è uno strumento molto vecchio, utilizzato inizialmente per i server di stampa (se non creato per ... Non ne sono sicuro).

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

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

Le estensioni degli standard si possono trovare in /etc/mime.types (sul mio desktop Debian GNU / man mime.types man file man mime.types e man mime.types . Forse devi installare l'utility file ei pacchetti mime-support ):

grep $( file -b --mime-type myfile.txt ) </etc/mime.types
text/plain      asc txt text pot brf srt

È possibile creare una funzione bash per determinare l'estensione corretta. C'è un piccolo campione (non perfetto):

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##*[/.-]}
}

Questa funzione potrebbe impostare una variabile Bash che può essere utilizzata in un secondo momento:

(Questo è ispirato alla risposta giusta di @Petesh):

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

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

È possibile forzare il taglio per visualizzare tutti i campi e quelli successivi aggiungendo - al numero del campo.

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

Quindi se FILE è eth0.pcap.gz , EXTENSION sarà pcap.gz

Usando la stessa logica, puoi anche recuperare il nome del file usando '-' con cut come segue:

NAME=`basename "$FILE" | cut -d'.' -f-1`

Funziona anche per i nomi di file che non hanno alcuna estensione.


Come estrarre il nome file e l'estensione nel fish :

function split-filename-extension --description "Prints the filename and extension"
  for file in $argv
    if test -f $file
      set --local extension (echo $file | awk -F. '{print $NF}')
      set --local filename (basename $file .$extension)
      echo "$filename $extension"
    else
      echo "$file is not a valid file"
    end
  end
end

Avvertenze: si divide sull'ultimo punto, che funziona bene per i nomi di file con punti al loro interno, ma non per le estensioni con punti in esse. Vedi l'esempio qui sotto.

Uso:

$ split-filename-extension foo-0.4.2.zip bar.tar.gz
foo-0.4.2 zip  # Looks good!
bar.tar gz  # Careful, you probably want .tar.gz as the extension.

Ci sono probabilmente modi migliori per farlo. Sentiti libero di modificare la mia risposta per migliorarla.

Se c'è una serie limitata di estensioni con cui ti occuperai e le conosci tutte, prova questo:

switch $file
  case *.tar
    echo (basename $file .tar) tar
  case *.tar.bz2
    echo (basename $file .tar.bz2) tar.bz2
  case *.tar.gz
    echo (basename $file .tar.gz) tar.gz
  # and so on
end

Questo non ha l'avvertimento come primo esempio, ma devi gestire ogni caso in modo che possa essere più noioso a seconda di quante estensioni ti puoi aspettare.


Di solito conosci già l'estensione, quindi potresti voler usare:

basename filename .extension

per esempio:

basename /path/to/dir/filename.txt .txt

e otteniamo

filename

Innanzitutto, ottieni il nome del file senza il percorso:

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

In alternativa, puoi concentrarti sull'ultima "/" del percorso anziché su "." che dovrebbe funzionare anche se hai estensioni di file imprevedibili:

filename="${fullfile##*/}"

Si consiglia di controllare la documentazione:


Io uso il seguente script

$ echo "foo.tar.gz"|rev|cut -d"." -f3-|rev
foo

Mellen scrive in un commento su un post del blog:

Usando Bash, c'è anche ${file%.*} Per ottenere il nome del file senza l'estensione e ${file##*.} Per ottenere l'estensione da solo. Questo è,

file="thisfile.txt"
echo "filename: ${file%.*}"
echo "extension: ${file##*.}"

Uscite:

filename: thisfile
extension: txt

Non c'è bisogno di disturbare con awk o sed o persino perl per questo semplice compito. Esiste una soluzione puramente Bash, os.path.splitext() che utilizza solo espansioni di parametri.

Implementazione di riferimento

Documentazione di os.path.splitext(path) :

Dividere il percorso del percorso in una coppia (root, ext) modo che root + ext == path , ed ext sia vuoto o inizi con un punto e contenga al massimo un punto. I periodi principali sul basename sono ignorati; splitext('.cshrc') restituisce ('.cshrc', '') .

Codice Python:

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

Implementazione di Bash

Onorare i periodi principali

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

Ignorando i periodi iniziali

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

test

Qui ci sono i casi di test per l'implementazione dei periodi principali di Ignoring , che dovrebbe corrispondere all'implementazione di riferimento di Python su ogni input.

|---------------|-----------|-------|
|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' |
|---------------|-----------|-------|

Risultati del test

Tutti i test sono passati.


Ok, quindi se ho capito bene, il problema qui è come ottenere il nome e l'estensione completa di un file che ha più estensioni, ad esempio, stuff.tar.gz .

Questo funziona per me:

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

Questo ti darà stuff come nome file e .tar.gz come estensione. Funziona con qualsiasi numero di estensioni, tra cui 0. Spero che questo aiuti per chiunque abbia lo stesso problema =)


Penso che se hai solo bisogno del nome del file, puoi provare questo:

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"

E questo è tutto = D.


Puoi usare la magia delle variabili POSIX:

bash-3.2$ FILENAME=somefile.tar.gz
bash-3.2$ echo ${FILENAME%%.*}
somefile
bash-3.2$ echo ${FILENAME%.*}
somefile.tar

C'è un avvertimento nel fatto che se il tuo nome file era nel formato ./somefile.tar.gz allora echo ${FILENAME%%.*} ./somefile.tar.gz avidamente la corrispondenza più lunga con il file . e avresti la stringa vuota.

(Puoi aggirare il problema con una variabile temporanea:

FULL_FILENAME=$FILENAME
FILENAME=${FULL_FILENAME##*/}
echo ${FILENAME%%.*}

)

Questo site spiega di più.

${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

[Revisionato da one-liner a una funzione bash generica, comportamento ora coerente con le utility dirname e basename ; motivazione aggiunta.]

La risposta accettata funziona bene nei casi tipici , ma fallisce nei casi limite , vale a dire:

  • Per i nomi di file senza estensione (chiamato suffisso nella parte restante di questa risposta), extension=${filename##*.} Restituisce il nome file di input anziché una stringa vuota.
  • extension=${filename##*.} non include l'iniziale . , contrariamente alla convenzione.
    • Davanti alla cieca . non funzionerebbe per i nomi di file senza suffisso.
  • filename="${filename%.*}" sarà la stringa vuota, se il nome del file di input inizia con . e non contiene oltre . caratteri (es. .bash_profile ) - contrariamente alla convenzione.

---------

Pertanto, la complessità di una soluzione robusta che copre tutti i casi limite richiede una funzione - vedere la sua definizione di seguito; può restituire tutti i componenti di un percorso .

Chiamata di esempio:

splitPath '/etc/bash.bashrc' dir fname fnameroot suffix
# -> $dir == '/etc'
# -> $fname == 'bash.bashrc'
# -> $fnameroot == 'bash'
# -> $suffix == '.bashrc'

Si noti che gli argomenti dopo il percorso di input sono liberamente scelti, nomi di variabili posizionali.
Per saltare le variabili non di interesse che precedono quelle che sono, specificare _ (usare la variabile throw-away $_ ) o '' ; ad esempio, per estrarre solo il nome file root e l'estensione, utilizzare splitPath '/etc/bash.bashrc' _ _ fnameroot extension .

# SYNOPSIS
#   splitPath path varDirname [varBasename [varBasenameRoot [varSuffix]]] 
# DESCRIPTION
#   Splits the specified input path into its components and returns them by assigning
#   them to variables with the specified *names*.
#   Specify '' or throw-away variable _ to skip earlier variables, if necessary.
#   The filename suffix, if any, always starts with '.' - only the *last*
#   '.'-prefixed token is reported as the suffix.
#   As with `dirname`, varDirname will report '.' (current dir) for input paths
#   that are mere filenames, and '/' for the root dir.
#   As with `dirname` and `basename`, a trailing '/' in the input path is ignored.
#   A '.' as the very first char. of a filename is NOT considered the beginning
#   of a filename suffix.
# EXAMPLE
#   splitPath '/home/jdoe/readme.txt' parentpath fname fnameroot suffix
#   echo "$parentpath" # -> '/home/jdoe'
#   echo "$fname" # -> 'readme.txt'
#   echo "$fnameroot" # -> 'readme'
#   echo "$suffix" # -> '.txt'
#   ---
#   splitPath '/home/jdoe/readme.txt' _ _ fnameroot
#   echo "$fnameroot" # -> 'readme'  
splitPath() {
  local _sp_dirname= _sp_basename= _sp_basename_root= _sp_suffix=
    # simple argument validation
  (( $# >= 2 )) || { echo "$FUNCNAME: ERROR: Specify an input path and at least 1 output variable name." >&2; exit 2; }
    # extract dirname (parent path) and basename (filename)
  _sp_dirname=$(dirname "$1")
  _sp_basename=$(basename "$1")
    # determine suffix, if any
  _sp_suffix=$([[ $_sp_basename = *.* ]] && printf %s ".${_sp_basename##*.}" || printf '')
    # determine basename root (filemane w/o suffix)
  if [[ "$_sp_basename" == "$_sp_suffix" ]]; then # does filename start with '.'?
      _sp_basename_root=$_sp_basename
      _sp_suffix=''
  else # strip suffix from filename
    _sp_basename_root=${_sp_basename%$_sp_suffix}
  fi
  # assign to output vars.
  [[ -n $2 ]] && printf -v "$2" "$_sp_dirname"
  [[ -n $3 ]] && printf -v "$3" "$_sp_basename"
  [[ -n $4 ]] && printf -v "$4" "$_sp_basename_root"
  [[ -n $5 ]] && printf -v "$5" "$_sp_suffix"
  return 0
}

test_paths=(
  '/etc/bash.bashrc'
  '/usr/bin/grep'
  '/Users/jdoe/.bash_profile'
  '/Library/Application Support/'
  'readme.new.txt'
)

for p in "${test_paths[@]}"; do
  echo ----- "$p"
  parentpath= fname= fnameroot= suffix=
  splitPath "$p" parentpath fname fnameroot suffix
  for n in parentpath fname fnameroot suffix; do
    echo "$n=${!n}"
  done
done

Codice di prova che esercita la funzione:

test_paths=(
  '/etc/bash.bashrc'
  '/usr/bin/grep'
  '/Users/jdoe/.bash_profile'
  '/Library/Application Support/'
  'readme.new.txt'
)

for p in "${test_paths[@]}"; do
  echo ----- "$p"
  parentpath= fname= fnameroot= suffix=
  splitPath "$p" parentpath fname fnameroot suffix
  for n in parentpath fname fnameroot suffix; do
    echo "$n=${!n}"
  done
done

Uscita prevista: annotare i casi limite:

  • un nome file senza suffisso
  • un nome di file che inizia con . ( non considerato l'inizio del suffisso)
  • un percorso di input che termina in / (trailing / viene ignorato)
  • un percorso di input che è solo un nome file ( . viene restituito come percorso principale)
  • un nome di file che ha più di - token prefissato (solo l'ultimo è considerato il suffisso):
----- /etc/bash.bashrc
parentpath=/etc
fname=bash.bashrc
fnameroot=bash
suffix=.bashrc
----- /usr/bin/grep
parentpath=/usr/bin
fname=grep
fnameroot=grep
suffix=
----- /Users/jdoe/.bash_profile
parentpath=/Users/jdoe
fname=.bash_profile
fnameroot=.bash_profile
suffix=
----- /Library/Application Support/
parentpath=/Library
fname=Application Support
fnameroot=Application Support
suffix=
----- readme.new.txt
parentpath=.
fname=readme.new.txt
fnameroot=readme.new
suffix=.txt

Dalle risposte sopra, il più breve oneliner per imitare Python

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

presumendo che il tuo file abbia davvero un'estensione, lo è

EXT="${PATH##*.}"; FILE=$(basename "$PATH" .$EXT)

IMHO è già stata data la migliore soluzione (usando l'espansione dei parametri della shell) e al momento è la migliore.

Aggiungo comunque questo che usa solo i comandi stupidi, che non è efficiente e che nessuno dovrebbe usare seriamente mai:

FILENAME=$(echo $FILE | cut -d . -f 1-$(printf $FILE | tr . '\n' | wc -l))
EXTENSION=$(echo $FILE | tr . '\n' | tail -1)

Aggiunto solo per divertimento :-)


Puoi usare

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

per ottenere il nome del file e

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

per ottenere l'estensione.

Test case:

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

Una risposta semplice:

Per espandere la risposta alle variabili POSIX , nota che puoi fare schemi più interessanti. Quindi, per il caso dettagliato qui, si potrebbe semplicemente fare questo:

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

Questo taglierà l'ultima occorrenza di .tar. <qualcosa> .

Più in generale, se si desidera rimuovere l'ultima occorrenza di. <qualcosa> . <qualcos'altro> allora

${1.*.*}

dovrebbe funzionare bene

Il collegamento sopra la risposta sembra essere morto. Ecco una grande spiegazione di una serie di manipolazioni delle stringhe che puoi eseguire direttamente in Bash, da TLDP .


Costruendo dalla risposta Petesh , se è necessario solo il nome del file, sia il percorso che l'estensione possono essere eliminati in una singola riga,

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

Ecco il codice con AWK . Può essere fatto più semplicemente. Ma non sono bravo in 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

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

Questo si occupa di più punti e spazi in un nome file, tuttavia se non ci sono estensioni restituisce il nome del file stesso. Facile da controllare però; prova solo che il nome file e l'estensione siano gli stessi.

Naturalmente questo metodo non funziona per i file .tar.gz. Tuttavia ciò potrebbe essere gestito in un processo in due fasi. Se l'estensione è gz, controlla di nuovo se c'è anche un'estensione tar.


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

funziona bene, quindi puoi usare solo:

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

A proposito, i comandi funzionano come segue.

Il comando per NAME sostituisce un "." carattere seguito da un numero qualsiasi di non- "." caratteri fino alla fine della riga, senza nulla (cioè rimuove tutto dall'ultimo "." alla fine della riga, incluso). Questa è fondamentalmente una sostituzione non avida usando l'enigma regex.

Il comando per EXTENSION sostituisce un qualsiasi numero di caratteri seguito da un "." carattere all'inizio della riga, senza nulla (cioè rimuove tutto dall'inizio della linea fino al punto finale incluso). Questa è una sostituzione golosa che è l'azione predefinita.





filenames