Vraag Pak bestandsnaam en extensie uit in Bash


Ik wil de bestandsnaam (zonder extensie) en de extensie apart krijgen.

De beste oplossing die ik tot nu toe heb gevonden is:

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

Dit is verkeerd omdat het niet werkt als de bestandsnaam meerdere bevat . karakters. Als, laten we zeggen, ik heb a.b.js, het zal overwegen a en b.js, in plaats van a.b en js.

Het kan gemakkelijk worden gedaan in Python met

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

maar ik zou er de voorkeur aan geven om, indien mogelijk, een Python-interpreter niet in te schakelen.

Heb je nog betere ideeën?


1614
2018-06-08 14:00


oorsprong


antwoorden:


Verkrijg eerst de bestandsnaam zonder het pad:

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

Als alternatief kunt u zich concentreren op de laatste '/' van het pad in plaats van de '.' welke zou moeten werken, zelfs als je onvoorspelbare bestandsextensies hebt:

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

2806
2018-06-08 14:05



~% FILE="example.tar.gz"
~% echo "${FILE%%.*}"
example
~% echo "${FILE%.*}"
example.tar
~% echo "${FILE#*.}"
tar.gz
~% echo "${FILE##*.}"
gz

Zie voor meer informatie shell parameter uitbreiding in de handleiding bij Bash.


502
2018-06-08 14:05



Meestal kent u de extensie al, dus misschien wilt u het volgende gebruiken:

basename filename .extension

bijvoorbeeld:

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

en we krijgen

filename

279
2017-10-19 10:56



U kunt de magie van POSIX-variabelen gebruiken:

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

Er is een voorbehoud in dat als uw bestandsnaam van het formulier was ./somefile.tar.gz dan echo ${FILENAME%%.*} zou gulzig de langste overeenkomst met de . en je zou de lege string hebben.

(Je kunt daar omheen werken met een tijdelijke variabele:

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

)


Deze plaats legt meer uit.

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

125
2018-02-05 09:09



Dat lijkt niet te werken als het bestand geen extensie of geen bestandsnaam heeft. Dit is wat ik gebruik; het maakt alleen gebruik van ingebouwde functies en verwerkt meer (maar niet alle) pathologische bestandsnamen.

#!/bin/bash
for fullpath in "$@"
do
    filename="${fullpath##*/}"                      # Strip longest match of */ from start
    dir="${fullpath:0:${#fullpath} - ${#filename}}" # Substring from 0 thru pos of filename
    base="${filename%.[^.]*}"                       # Strip shortest match of . plus at least one non-dot char from end
    ext="${filename:${#base} + 1}"                  # Substring from len of base thru end
    if [[ -z "$base" && -n "$ext" ]]; then          # If we have an extension and no base, it's really the base
        base=".$ext"
        ext=""
    fi

    echo -e "$fullpath:\n\tdir  = \"$dir\"\n\tbase = \"$base\"\n\text  = \"$ext\""
done

En hier zijn enkele testcases:

$ basename-and-extension.sh / / home / me / / home / me / file /home/me/file.tar /home/me/file.tar.gz /home/me/.hidden / home / me / .hidden.tar / home / mij / ...
/:
    dir = "/"
    base = ""
    ext = ""
/ Home / me /:
    dir = "/ home / me /"
    base = ""
    ext = ""
/ Home / me / file:
    dir = "/ home / me /"
    base = "bestand"
    ext = ""
/home/me/file.tar:
    dir = "/ home / me /"
    base = "bestand"
    ext = "tar"
/home/me/file.tar.gz:
    dir = "/ home / me /"
    base = "file.tar"
    ext = "gz"
/home/me/.hidden:
    dir = "/ home / me /"
    base = ".hidden"
    ext = ""
/home/me/.hidden.tar:
    dir = "/ home / me /"
    base = ".hidden"
    ext = "tar"
/ Home / me / ..:
    dir = "/ home / me /"
    base = ".."
    ext = ""
.:
    dir = ""
    base = "."
    ext = ""

65
2017-09-10 05:17



Je kunt gebruiken basename.

Voorbeeld:

$ basename foo-bar.tar.gz .tar.gz
foo-bar

U moet basename de extensie geven die moet worden verwijderd, maar als u altijd bezig bent met uitvoeren tar met -z dan weet je dat de extensie zal zijn .tar.gz.

Dit zou moeten doen wat je wilt:

tar -zxvf $1
cd $(basename $1 .tar.gz)

40
2018-02-05 08:50



Mellen schrijft in een reactie op een blogpost:

Met Bash is er ook ${file%.*} om de bestandsnaam te krijgen zonder de extensie en ${file##*.} om de extensie alleen te krijgen. Dat is,

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

uitgangen:

filename: thisfile
extension: txt

24
2017-07-21 10:24



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

werkt prima, dus je kunt gewoon gebruiken:

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

De opdrachten werken trouwens als volgt.

Het commando voor NAME vervangt een "." teken gevolgd door een willekeurig aantal niet-"." tekens tot het einde van de regel, met niets (d.w.z. het verwijdert alles uit de finale "."tot het einde van de regel, inclusief). Dit is eigenlijk een niet-hebzuchtige vervanging met behulp van regex-bedrog.

Het commando voor EXTENSION vervangt een willekeurig aantal tekens gevolgd door een "." teken aan het begin van de regel, met niets (d.w.z. het verwijdert alles van het begin van de regel tot de laatste punt, inclusief). Dit is een hebzuchtige vervanging die de standaardactie is.


24
2018-06-08 14:14



Je zou de kunnen gebruiken cut commando om de laatste twee extensies te verwijderen (de ".tar.gz" een deel):

$ echo "foo.tar.gz" | cut -d'.' --complement -f2-
foo

Zoals opgemerkt door Clayton Hughes in een opmerking, zal dit niet werken voor het eigenlijke voorbeeld in de vraag. Dus als alternatief stel ik voor om te gebruiken sed met uitgebreide reguliere expressies, zoals deze:

$ echo "mpc-1.0.1.tar.gz" | sed -r 's/\.[[:alnum:]]+\.[[:alnum:]]+$//'
mpc-1.0.1

Het werkt door de laatste twee (alfanumerieke) extensies onvoorwaardelijk te verwijderen.

[Opnieuw bijgewerkt na commentaar van Anders Lindahl]


22
2018-02-05 08:53



Hier zijn enkele alternatieve suggesties (meestal in awk), inclusief enkele geavanceerde use-cases, zoals het uitpakken van versienummers voor softwarepakketten.

f='/path/to/complex/file.1.0.1.tar.gz'

# Filename : 'file.1.0.x.tar.gz'
    echo "$f" | awk -F'/' '{print $NF}'

# Extension (last): 'gz'
    echo "$f" | awk -F'[.]' '{print $NF}'

# Extension (all) : '1.0.1.tar.gz'
    echo "$f" | awk '{sub(/[^.]*[.]/, "", $0)} 1'

# Extension (last-2): 'tar.gz'
    echo "$f" | awk -F'[.]' '{print $(NF-1)"."$NF}'

# Basename : 'file'
    echo "$f" | awk '{gsub(/.*[/]|[.].*/, "", $0)} 1'

# Basename-extended : 'file.1.0.1.tar'
    echo "$f" | awk '{gsub(/.*[/]|[.]{1}[^.]+$/, "", $0)} 1'

# Path : '/path/to/complex/'
    echo "$f" | awk '{match($0, /.*[/]/, a); print a[0]}'
    # or 
    echo "$f" | grep -Eo '.*[/]'

# Folder (containing the file) : 'complex'
    echo "$f" | awk -F'/' '{$1=""; print $(NF-1)}'

# Version : '1.0.1'
    # Defined as 'number.number' or 'number.number.number'
    echo "$f" | grep -Eo '[0-9]+[.]+[0-9]+[.]?[0-9]?'

    # Version - major : '1'
    echo "$f" | grep -Eo '[0-9]+[.]+[0-9]+[.]?[0-9]?' | cut -d. -f1

    # Version - minor : '0'
    echo "$f" | grep -Eo '[0-9]+[.]+[0-9]+[.]?[0-9]?' | cut -d. -f2

    # Version - patch : '1'
    echo "$f" | grep -Eo '[0-9]+[.]+[0-9]+[.]?[0-9]?' | cut -d. -f3

# All Components : "path to complex file 1 0 1 tar gz"
    echo "$f" | awk -F'[/.]' '{$1=""; print $0}'

# Is absolute : True (exit-code : 0)
    # Return true if it is an absolute path (starting with '/' or '~/'
    echo "$f" | grep -q '^[/]\|^~/'

Alle use-cases gebruiken het originele volledige pad als invoer, zonder afhankelijk te zijn van tussentijdse resultaten.


19
2018-06-16 09:02



U hoeft zich niet bezig te houden met awk of sed of zelfs perl voor deze eenvoudige taak. Er is een pure Bash, os.path.splitext()-compatibele oplossing die alleen parameteruitbreidingen gebruikt.

Referentie-implementatie

Documentatie van os.path.splitext(path):

Splits het pad van de padnaam op in een paar (root, ext) zoals dat root + ext == path, en ext is leeg of begint met een punt en bevat maximaal één periode. Voorlopende perioden op de basename worden genegeerd; splitext('.cshrc') komt terug ('.cshrc', '').

Python-code:

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

Bash-implementatie

Voorlopende periodes eren

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

Voorlopende perioden negeren

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

Tests

Hier zijn testgevallen voor de Voorlopende perioden negeren implementatie, die overeen moet komen met de Python-referentie-implementatie op elke invoer.

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

Test resultaten

Alle tests zijn geslaagd.


18
2017-12-02 09:04