Как удалить суффикс файла и часть пути из строки пути в 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}раздел соответствия задней части.

Зан Рысь
источник
Я закончил тем, что использовал это, потому что это было самым гибким, и было несколько других подобных вещей, которые я хотел сделать, и это сделало хорошо.
Лоуренс Джонстон
Это, пожалуй, самый гибкий из всех опубликованных ответов, но я думаю, что ответы, предлагающие команды basename и dirname, также заслуживают некоторого внимания. Они могут быть просто уловкой, если вам не нужно какое-либо другое причудливое сопоставление с образцом.
mgadda
7
Что это называется $ {x% .bar}? Я хотел бы узнать больше об этом.
Василий
14
@Basil: расширение параметров. В консоли введите «man bash», а затем введите «/ extension extension»
Zan Lynx
1
Я думаю, что объяснение «man bash» имеет смысл, если вы уже знаете, что он делает, или если вы сами попробовали его. Это почти так же плохо, как Git Reference. Я бы просто погуглил.
triplebig
227

посмотрите на команду basename:

NAME="$(basename /foo/fizzbuzz.bar .bar)"
zigdon
источник
11
Вероятно, самое простое из всех предлагаемых в настоящее время решений ... хотя я бы использовал $ (...) вместо обратных кавычек.
Майкл Джонсон
6
Проще всего, но добавляет зависимость (не огромную или странную, я признаю). Также необходимо знать суффикс.
Винко Врсалович
И может использоваться для удаления чего-либо с конца, в основном это просто удаление строки с конца.
Смар
2
Проблема во времени. Я только что искал вопрос для этого обсуждения после просмотра bash почти 5 минут, чтобы обработать 800 файлов, используя базовое имя. Используя вышеупомянутый метод регулярных выражений, время было сокращено примерно до 7 секунд. Хотя этот ответ проще для программиста, время просто слишком велико. Представьте себе папку с парой тысяч файлов в ней! У меня есть несколько таких папок.
xizdaqrian
@xizdaqrian Это абсолютно неверно. Это простая программа, которая не должна возвращаться за полсекунды. Я только что выполнил поиск времени / home / me / dev -name "* .py" .py -exec basename {} \; и это лишило расширение и каталог для 1500 файлов за 1 секунду всего.
Ласло Трешкай
40

Чистый bash, сделанный в двух отдельных операциях:

  1. Удалить путь из строки пути:

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

    base=${file%.*}
    #${base} is now 'file'.
Param
источник
18

Используя basename, я использовал следующее для достижения этой цели:

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

    # Do things with $fname
done;

Это не требует априорных знаний о расширении файла и работает, даже если у вас есть имя файла с точками в имени файла (перед его расширением); basenameХотя для этого требуется программа , но это часть ядра GNU Coreutils, поэтому она должна поставляться с любым дистрибутивом.

Майк
источник
1
Отличный ответ! удаляет расширение очень чистым способом, но не удаляет. в конце имени файла.
metrix
3
@metrix просто добавь "." до $ ext, то есть: fname=`basename $file .$ext`
Карлос Тронкосо
13

Pure Bash путь:

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

Эта функциональность описана на man bash в разделе «Расширение параметров». Существует множество способов, не связанных с bash: awk, perl, sed и так далее.

РЕДАКТИРОВАТЬ: работает с точками в суффиксах файлов и не нужно знать суффикс (расширение), но не работает с точками в самом имени .

Винко Врсалович
источник
11

Функции basename и dirname - это то, что вам нужно:

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
Jerub
источник
1
Было бы полезно исправить здесь кавычки - возможно, запустите это через shellcheck.netmystring=$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
Это хорошо и надежно
Гаурав Джайн
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'

Что даст вам выход

FizzBuzz

nymacro
источник
Хотя это и является ответом на исходный вопрос, эта команда полезна, когда у меня есть строки путей в файле для извлечения базовых имен и их вывода на экран.
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если вы знаете расширение) гораздо проще.

mivk
источник
1

Базовое имя делает это, удаляет путь. Он также удалит суффикс, если он задан, и, если он совпадает с суффиксом файла, но вам необходимо знать суффикс, который нужно дать команде. В противном случае вы можете использовать mv и выяснить, какое новое имя должно быть другим способом.

waynecolvin
источник
1

Объединяя ответ с наивысшим рейтингом и ответ с наивысшим рейтингом, чтобы получить имя файла без полного пути:

$ x="/foo/fizzbuzz.bar.quux"
$ y=(`basename ${x%%.*}`)
$ echo $y
fizzbuzz
c.gutierrez
источник
Почему вы используете массив здесь? Кроме того, зачем вообще использовать basename?
codeforester