Як видалити суфікс файлу та частину шляху із рядка шляху в Bash?


396

Враховуючи шлях до файлу рядків, наприклад /foo/fizzbuzz.bar, як я використовую bash, щоб витягти лише fizzbuzzчастину зазначеного рядка?


Інформація, яку ви можете знайти в посібнику Bash , шукайте ${parameter%word}та ${parameter%%word}в розділі відповідності деталей.
1ac0

Відповіді:


574

Ось як це зробити з операторами # і% в Bash.

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

${x%.bar}також може бути ${x%.*}видалити все після крапки або ${x%%.*}видалити все після першої точки.

Приклад:

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

Документацію можна знайти в посібнику Bash . Шукайте розділ узгодження частини ${parameter%word}та ${parameter%%word}контур.


Я в кінцевому підсумку скористався цим, тому що він був найбільш гнучким, і було ще пару подібних речей, які я хотів зробити так само, як це було добре.
Лоуренс Джонстон

Це, мабуть, найбільш гнучка з усіх опублікованих відповідей, але я думаю, що відповіді, що пропонують команди базового іменного ім’я та ім’я, також заслуговують на певну увагу. Вони можуть бути лише хитрістю, якщо вам не потрібне якесь інше вигадливе узгодження шаблону.
mgadda

7
Як це називається $ {x% .bar}? Я хотів би дізнатися більше про це.
Василь

14
@Basil: Розширення параметрів. На консолі введіть "man bash", а потім наберіть "/ Параметр розширення"
Zan Lynx

1
Я думаю, що пояснення 'man bash' має сенс, якщо ви вже знаєте, що це робить, або якщо ви спробували це на самому важкому шляху. Це майже так само погано, як і посилання на git. Я б замість цього просто google.
тричі велика

226

подивіться на команду basename:

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

11
Напевно, найпростіші з усіх пропонованих на даний момент рішень ... хоча я б використовував $ (...) замість беккетів.
Майкл Джонсон

6
Найпростіша, але додає залежність (не величезна чи дивна, я визнаю). Тут також потрібно знати суфікс.
Вінко Врсалович

І може бути використаний для видалення будь-чого з кінця, в основному це просто видалення рядка з кінця.
Смар

2
Проблема - час удару. Я просто шукав питання для цього обговорення після перегляду bash, який займає майже 5 хвилин для обробки 800 файлів, використовуючи базове ім'я. Використовуючи вищевказаний метод регексу, час було скорочено до приблизно 7 сек. Незважаючи на те, що цю відповідь програмісту простіше виконати, часу на враження просто небагато. Уявіть собі папку з парою тисяч файлів у ній! У мене є кілька таких папок.
xizdaqrian

@xizdaqrian Це абсолютно помилково. Це проста програма, для повернення якої не повинно піти пів секунди. Я щойно виконав час find / home / me / dev -name "* .py" .py -exec basename {} \; і він позбавив розширення та каталог для 1500 файлів за 1 секунду.
Ласло Трешкай

40

Чистий баш, виконаний у двох окремих операціях:

  1. Видаліть шлях із рядка шляху:

    path=/foo/bar/bim/baz/file.gif
    
    file=${path##*/}  
    #$file is now 'file.gif'
  2. Видаліть розширення з рядка шляху:

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

18

Використовуючи базове ім'я, я досяг цього:

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

    # Do things with $fname
done;

Це не вимагає апріорних знань про розширення файлу і працює навіть у тому випадку, коли у вас є ім'я файлу, яке має в ньому ім’я файлів (перед його розширенням); вона вимагає програми basename, але це є частиною GNU coreutils, тому вона повинна поставлятися з будь-яким дистрибутивом.


1
Відмінна відповідь! видаляє розширення дуже чистим способом, але він не видаляє. в кінці імені файлу.
метрика

3
@metrix просто додайте "." до $ ext, тобто: fname=`basename $file .$ext`
Карлос Тронкосо

13

Чистий баш спосіб:

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

Ця функціональність пояснюється в man bash в розділі "Розширення параметрів". Небасових способів багато: awk, perl, sed і так далі.

EDIT: Працює з крапками у суфіксах файлів і не потрібно знати суфікс (розширення), але не працює з крапками в самому імені .


11

Функції базового імені та ім’я файлів - це те, що вам потрібно:

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

Має вихід:

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

1
Було б корисно виправити тут цитування - можливо, запустіть це через shellcheck.net з, mystring=$1а не поточним постійним значенням (яке придушить кілька попереджень, будучи певним, що вони не містять пробілів / символів глобуса / тощо), і вирішіть його проблеми знахідки?
Чарльз Даффі

Ну, я вніс декілька відповідних змін для підтримки лапок у $ mystring. Боже, це було давно, я це писав :)
Джеруб

Буде подальше вдосконалення, щоб процитувати результати: echo "basename: $(basename "$mystring")"- таким чином, якщо після завершення mystring='/foo/*'ви не *заміните список файлів у поточному каталозі . basename
Чарльз Даффі

6

Використання basenameпередбачає, що ви знаєте, що таке розширення файлу, чи не так?

І я вважаю, що різні пропозиції регулярних виразів не справляються з іменем файлу, що містить більше одного "."

Наступне, здається, справляється з подвійними крапками. О, і назви файлів, які містять самі "/" (лише для ударів)

Перефразовуючи Паскаля, "Вибачте, цей сценарій такий довгий. Я не встиг його скоротити"


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

1
Це приємно і міцно
Gaurav Jain

4

На додаток до відповідного синтаксису POSIX, який використовується у цій відповіді ,

basename string [suffix]

як і в

basename /foo/fizzbuzz.bar .bar

GNUbasename підтримує інший синтаксис:

basename -s .bar /foo/fizzbuzz.bar

з тим же результатом. Різниця і перевага полягає в тому , що це -sозначає -a, що підтримує кілька аргументів:

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

Це навіть можна зробити безпечним для імен файлів, розділивши вихідний сигнал з байтами NUL, використовуючи -zпараметр, наприклад, для цих файлів, що містять пробіли, нові рядки та символи глобу (цитується ls):

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

Читання в масив:

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

readarray -dпотрібен Bash 4.4 або новіший. Для старих версій нам потрібно зробити цикл:

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

Також вказаний суфікс видаляється у висновку, якщо він присутній (і ігнорується інакше).
aksh1618


3

Якщо ви не можете використовувати базове ім'я, як пропонується в інших публікаціях, ви завжди можете використовувати sed. Ось (потворний) приклад. Він не найбільший, але він працює, витягуючи шуканий рядок і замінюючи вхід шуканим рядком.

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

Що отримає вам вихід

фізбуз


Хоча це відповідь на оригінальне запитання, ця команда корисна, коли у мене є рядки шляхів у файлі для вилучення базових імен, щоб надрукувати їх на екран.
Sangcheol Choi

2

Остерігайтеся запропонованого рішення perl: воно видаляє все після першої крапки.

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

Якщо ви хочете зробити це за допомогою perl, це працює:

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

Але якщо ви використовуєте Bash, рішення з y=${x%.*}(або basename "$x" .extякщо ви знаєте розширення) набагато простіші.


1

Базове ім'я це робить, видаляє шлях. Він також видалить суфікс, якщо він вказаний, і якщо він відповідає суфіксу файлу, але вам потрібно знати суфікс, який слід надати команді. В іншому випадку ви можете використовувати mv і розібратися, яке нове ім’я має бути іншим способом.


1

Поєднуючи відповідь із найвищим рейтингом та відповідь другого рейтингу, щоб отримати ім'я файлу без повного шляху:

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

Чому ви тут використовуєте масив? Крім того, навіщо взагалі використовувати базове ім’я?
codeforester
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.