Jak usunąć sufiks pliku i część ścieżki z ciągu ścieżki w Bash?

396

Biorąc pod uwagę ścieżkę do pliku łańcuchowego, na przykład /foo/fizzbuzz.bar, w jaki sposób użyłbym bash do wyodrębnienia tylko fizzbuzzczęści tego łańcucha?

Lawrence Johnston
źródło
Informacje, które można znaleźć w instrukcji Bash , poszukać ${parameter%word}i ${parameter%%word}w części dotyczącej dopasowywania części końcowej.
1ac0

Odpowiedzi:

574

Oto jak to zrobić za pomocą operatorów # i% w Bash.

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

${x%.bar}może być również ${x%.*}usunięcie wszystkiego po kropce lub ${x%%.*}usunięcie wszystkiego po pierwszej kropce.

Przykład:

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

Dokumentację można znaleźć w instrukcji Bash . Szukaj ${parameter%word}i ${parameter%%word}dopasowuj sekcję dopasowywania porcji.

Zan Lynx
źródło
Skończyło się na tym, ponieważ był najbardziej elastyczny i było kilka innych podobnych rzeczy, które chciałem zrobić, a także to, co zrobiłem dobrze.
Lawrence Johnston,
Jest to prawdopodobnie najbardziej elastyczna ze wszystkich opublikowanych odpowiedzi, ale myślę, że odpowiedzi sugerujące polecenia basename i dirname również zasługują na uwagę. Mogą być po prostu sztuczką, jeśli nie potrzebujesz żadnego innego fantazyjnego dopasowania wzoru.
mgadda,
7
Co to nazywa się $ {x% .bar}? Chciałbym dowiedzieć się więcej na ten temat.
Basil
14
@Basil: Rozszerzenie parametru. Na konsoli wpisz „man bash”, a następnie wpisz „/ parameter extension”
Zan Lynx
1
Wydaje mi się, że wyjaśnienie „man bash” ma sens, jeśli już wiesz, co to robi lub jeśli sam to wypróbowałeś. Jest prawie tak zły, jak referencje git. Zamiast tego po prostu google.
triplebig
226

spójrz na polecenie basename:

NAME="$(basename /foo/fizzbuzz.bar .bar)"
zigdon
źródło
11
Prawdopodobnie najprostsze ze wszystkich obecnie oferowanych rozwiązań ... chociaż użyłbym $ (...) zamiast backticks.
Michael Johnson,
6
Najprostsze, ale dodaje zależność (przyznaję, że nie jest to duża ani dziwna). Musi także znać sufiks.
Vinko Vrsalovic
I może być użyty do usunięcia czegokolwiek z końca, w zasadzie usuwa po prostu ciąg znaków z końca.
Smar,
2
Problemem jest czas uderzenia. Właśnie przeszukałem pytanie w tej dyskusji po tym, jak bash zajął prawie 5 minut na przetworzenie 800 plików przy użyciu basename. Przy użyciu powyższej metody wyrażenia regularnego czas został skrócony do około 7 sekund. Chociaż odpowiedź ta jest łatwiejsza dla programisty, czas na nią jest po prostu zbyt duży. Wyobraź sobie folder zawierający kilka tysięcy plików! Mam kilka takich folderów.
xizdaqrian
@xizdaqrian To absolutnie nieprawda. Jest to prosty program, którego powrót nie powinien zająć pół sekundy. Właśnie wykonałem czas find / home / me / dev -name "* .py" .py -exec basename {} \; i usunął rozszerzenie i katalog dla 1500 plików w sumie 1 sekundę.
Laszlo Treszkai,
40

Pure Bash, wykonane w dwóch osobnych operacjach:

  1. Usuń ścieżkę z ciągu-ścieżki:

    path=/foo/bar/bim/baz/file.gif
    
    file=${path##*/}  
    #$file is now 'file.gif'
  2. Usuń rozszerzenie z ciągu ścieżki:

    base=${file%.*}
    #${base} is now 'file'.
Param
źródło
18

Używając basename, wykorzystałem następujące do osiągnięcia tego:

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

    # Do things with $fname
done;

Nie wymaga to a priori znajomości rozszerzenia pliku i działa nawet wtedy, gdy nazwa pliku zawiera kropki w nazwie pliku (przed rozszerzeniem); wymaga to basenamejednak programu , ale jest to część jądra GNU, więc powinno być dostarczane z dowolną dystrybucją.

mikrofon
źródło
1
Doskonała odpowiedź! usuwa rozszerzenie w bardzo czysty sposób, ale nie usuwa. na końcu nazwy pliku.
metrix
3
@metrix wystarczy dodać „.” przed $ ext, tj .: fname=`basename $file .$ext`
Carlos Troncoso
13

Pure Bash Way:

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

Ta funkcjonalność została wyjaśniona w podręczniku man bash w „Rozszerzeniu parametrów”. Istnieje wiele sposobów na bash: awk, perl, sed i tak dalej.

EDYCJA: Działa z kropkami w sufiksach plików i nie musi znać sufiksu (rozszerzenia), ale nie działa z kropkami w samej nazwie .

Vinko Vrsalovic
źródło
11

Funkcje basename i dirname są tym, czego szukasz:

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

Ma moc wyjściową:

basename: fizzbuzz.bar
basename + remove .bar: fizzbuzz
dirname: /foo
Jerub
źródło
1
Dobrze byłoby, aby naprawić przytoczyć tutaj - może uruchomić to przez shellcheck.net ze mystring=$1zamiast aktualnej wartości stałej (które tłumią kilka ostrzeżeń, będąc pewny, aby nie zawierać spacji / znaków glob / etc), oraz adres zagadnień IT znajdzie?
Charles Duffy
Cóż, wprowadziłem odpowiednie zmiany w celu obsługi znaków cudzysłowu w $ mystring. Boże, to było dawno temu, napisałem to :)
Jerub
Dalszą poprawą byłoby cytowanie wyników: echo "basename: $(basename "$mystring")"- w ten sposób, jeśli po zakończeniu mystring='/foo/*'nie zostanie *zastąpiona lista plików w bieżącym katalogu . basename
Charles Duffy
6

Używanie basenamezakłada, że ​​wiesz, jakie jest rozszerzenie pliku, prawda?

I wierzę, że różne propozycje wyrażeń regularnych nie radzą sobie z nazwą pliku zawierającą więcej niż jeden „.”

Poniższe wydaje się radzić sobie z podwójnymi kropkami. Aha, i nazwy plików, które same zawierają „/” (tylko dla kopnięć)

Parafrazując Pascala, „Przepraszam, że ten skrypt jest tak długi. Nie miałem czasu, aby go skrócić”


  #!/usr/bin/perl
  $fullname = $ARGV[0];
  ($path,$name) = $fullname =~ /^(.*[^\\]\/)*(.*)$/;
  ($basename,$extension) = $name =~ /^(.*)(\.[^.]*)$/;
  print $basename . "\n";
 
Andrew Edgecombe
źródło
1
To jest miłe i solidne
Gaurav Jain
4

Oprócz składni zgodnej z POSIX zastosowanej w tej odpowiedzi ,

basename string [suffix]

jak w

basename /foo/fizzbuzz.bar .bar

GNUbasename obsługuje inną składnię:

basename -s .bar /foo/fizzbuzz.bar

z tym samym wynikiem. Różnica i zaletą jest to -simplikuje -a, który obsługuje wiele argumentów:

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

Można to nawet zabezpieczyć pod nazwą pliku, oddzielając dane wyjściowe bajtami NUL za pomocą -zopcji, na przykład dla plików zawierających spacje, znaki nowej linii i znaki globalne (cytowane przez ls):

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

Odczytywanie do tablicy:

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

readarray -dwymaga wersji Bash 4.4 lub nowszej. W przypadku starszych wersji musimy zapętlić:

while IFS= read -r -d '' fname; do arr+=("$fname"); done < <(basename -zs .bar has*)
Benjamin W.
źródło
Ponadto określony sufiks jest usuwany z wyjścia, jeśli jest obecny (i ignorowany w inny sposób).
aksh1618
3
perl -pe 's/\..*$//;s{^.*/}{}'
mopoke
źródło
3

Jeśli nie możesz użyć basename jak sugerowano w innych postach, zawsze możesz użyć sed. Oto (brzydki) przykład. Nie jest najlepszy, ale działa poprzez wyodrębnienie poszukiwanego ciągu i zastąpienie wejścia żądanym ciągiem.

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

Co da ci wynik

fizzbuzz

Nymacro
źródło
Chociaż jest to odpowiedź na pierwotne pytanie, to polecenie jest przydatne, gdy w pliku mam linie ścieżek do wyodrębnienia podstawowych nazw i wydrukowania ich na ekranie.
Sangcheol Choi,
2

Uwaga na sugerowane rozwiązanie perla: usuwa wszystko po pierwszej kropce.

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

Jeśli chcesz to zrobić za pomocą Perla, działa to:

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

Ale jeśli używasz Basha, rozwiązania z y=${x%.*}(lub basename "$x" .extjeśli znasz rozszerzenie) są znacznie prostsze.

mivk
źródło
1

Basename to robi, usuwa ścieżkę. Usunie również sufiks, jeśli jest podany i jeśli pasuje do sufiksu pliku, ale trzeba znać przyrostek, aby podać polecenie. W przeciwnym razie możesz użyć mv i dowiedzieć się, jaka powinna być nowa nazwa.

waynecolvin
źródło
1

Łącząc najwyżej ocenianą odpowiedź z drugą najwyżej ocenianą odpowiedzią, aby uzyskać nazwę pliku bez pełnej ścieżki:

$ x="/foo/fizzbuzz.bar.quux"
$ y=(`basename ${x%%.*}`)
$ echo $y
fizzbuzz
c.gutierrez
źródło
Dlaczego używasz tutaj tablicy? Ponadto, po co w ogóle używać basename?
codeforester