Как я могу удалить расширение имени файла в сценарии оболочки?

147

Что не так со следующим кодом?

name='$filename | cut -f1 -d'.''

Как есть, я получаю буквенную строку $filename | cut -f1 -d'.', но если я удаляю кавычки, я ничего не получаю. Между тем, набрав

"test.exe" | cut -f1 -d'.'

в оболочке дает мне вывод, который я хочу test. Я уже знаю $filename, было назначено правильное значение. Я хочу назначить переменной имя файла без расширения.

mimicocotopus
источник
6
basename $filename .exeсделал бы то же самое. Это при условии, что вы всегда знаете, какое расширение вы хотите удалить.
mpe
7
@ mpe, ты имеешь в виду basename "$filename" .exe. В противном случае имена файлов с пробелами будут плохими новостями.
Чарльз Даффи

Ответы:

114

Вы должны использовать синтаксис подстановки команд,$(command) когда хотите выполнить команду в сценарии / команде.

Так что ваша линия будет

name=$(echo "$filename" | cut -f 1 -d '.')

Объяснение кода:

  1. echoполучить значение переменной $filenameи отправить его на стандартный вывод
  2. Затем мы получаем вывод и передаем его cutкоманде
  3. cutБудет использовать. в качестве разделителя (также известного как разделитель) для разрезания строки на сегменты и -fвыбора того, какой сегмент мы хотим иметь в выводе
  4. Тогда $()подстановка команды получит вывод и вернет его значение
  5. Возвращенное значение будет присвоено переменной с именем name

Обратите внимание, что это дает часть переменной до первого периода .:

$ filename=hello.world
$ echo "$filename" | cut -f 1 -d '.'
hello
$ filename=hello.hello.hello
$ echo "$filename" | cut -f 1 -d '.'
hello
$ filename=hello
$ echo "$filename" | cut -f 1 -d '.'
hello
Рохан
источник
Спасибо. Я также заметил, что мне нужно использовать команду echo. name =echo $filename | cut -f1 -d'.'
mimicocotopus
17
Backticks не рекомендуется POSIX, $()является предпочтительным.
Иордания
3
Форкинг и прокачка нескольких персонажей - самое худшее из возможных решений.
Йенс
39
Проблема с этим ответом состоит в том, что предполагается, что входная строка имеет ТОЛЬКО одну точку ... @chepner ниже имеет гораздо лучшее решение ... name = $ {filename%. *}
Скотт Стенсланд
4
Этот ответ характерен для начинающих и не должен распространяться. Используйте механизм встроенной , как описано chepner в ответ
neric
261

Вы также можете использовать расширение параметров:

$ filename=foo.txt
$ echo "${filename%.*}"
foo
chepner
источник
6
вот объяснение команды: gnu.org/software/bash/manual/html_node/…
Карлос Роблес
1
И здесь я собирался использовать echo -n "This.File.Has.Periods.In.It.txt" | awk -F. '{$NF=""; print $0}' | tr ' ' '.' | rev | cut -c 2- | rev. Спасибо.
user208145
1
Это работает с файлами с несколькими расширениями, как image.png.gz?
Hawker65
11
%.*удалит только последнее расширение; если вы хотите удалить все расширения, используйте %%.*.
chepner
1
Кажется, это работает лучше, чем принятый ответ. Он удаляет последнее расширение только в том случае, если файл имеет более одной точки, в то время как cut удаляет все после первой точки.
Дейл Андерсон
117

Если вы знаете расширение, вы можете использовать базовое имя

$ basename /home/jsmith/base.wiki .wiki
base
Стивен Пенни
источник
Как я могу удалить только .wikiи в конечном итоге /home/jsmith/base?
Габриэль Стейплс
Обновление: ответ: см. Stackoverflow.com/a/32584935/4561887 . Работает отлично!
Габриэль Стейплс
20

Если ваше имя файла содержит точку (отличную от расширения), используйте это:

echo $filename | rev | cut -f 2- -d '.' | rev
manish_s
источник
1
Я забыл средний обороты, но как только увидел, это было здорово!
высший пооба
Еще лучше, если -sзадана опция cut, так что она возвращает пустую строку всякий раз, когда имя файла не содержит точки.
Hibou57
1
На мой взгляд, это должен быть принятый ответ, поскольку он работает с путями с точками в них, со скрытыми файлами, начинающимися с точки, или даже с файлами с несколькими расширениями.
Тим Криф
20
file1=/tmp/main.one.two.sh
t=$(basename "$file1")                        # output is main.one.two.sh
name=$(echo "$file1" | sed -e 's/\.[^.]*$//') # output is /tmp/main.one.two
name=$(echo "$t" | sed -e 's/\.[^.]*$//')     # output is main.one.two

используйте то, что вы хотите. Здесь я предполагаю, что последняя .(точка), за которой следует текст, является расширением.

Raghwendra
источник
6
#!/bin/bash
file=/tmp/foo.bar.gz
echo $file ${file%.*}

выходы:

/tmp/foo.bar.gz /tmp/foo.bar

Обратите внимание, что удаляется только последнее расширение.

C. Пол Бонд
источник
3

Моя рекомендация заключается в использовании basename.
Это по умолчанию в Ubuntu, визуально простой код и имеет дело с большинством случаев.

Вот некоторые подслучаи для работы с пробелами и многоточечным / под-расширением:

pathfile="../space fld/space -file.tar.gz"
echo ${pathfile//+(*\/|.*)}

Обычно он сначала избавляется от расширения ., но на нашем ..пути терпит неудачу

echo **"$(basename "${pathfile%.*}")"**  
space -file.tar     # I believe we needed exatly that

Вот важное примечание:

Я использовал двойные кавычки внутри двойных кавычек, чтобы иметь дело с пробелами. Одиночная кавычка не пройдет из-за отправки $. Bash необычен и читает «вторые» первые «цитаты» из-за расширения.

Тем не менее, вам все еще нужно думать о .hidden_files

hidden="~/.bashrc"
echo "$(basename "${hidden%.*}")"  # will produce "~" !!!  

не ожидаемый "" результат. Чтобы это произошло, используйте $HOMEили /home/user_path/
потому что bash снова "необычен" и не расширяет "~" (поиск bash BashPitfalls)

hidden2="$HOME/.bashrc" ;  echo '$(basename "${pathfile%.*}")'
Алексей К.
источник
1
#!/bin/bash
filename=program.c
name=$(basename "$filename" .c)
echo "$name"

выходы:

program
гадостей
источник
Чем это отличается от ответа, данного Стивеном Пенни 3 года назад?
gniourf_gniourf
1

Как указал Hawker65 в комментарии к ответу chepner, решение с наибольшим количеством голосов не заботится ни о нескольких расширениях (например, filename.tar.gz), ни о точках в остальной части пути (например, this.path / with). .dots / in.path.name). Возможное решение:

a=this.path/with.dots/in.path.name/filename.tar.gz
echo $(dirname $a)/$(basename $a | cut -d. -f1)
MadMage
источник
Он удаляет tar.gz, выбирая символы перед первым экземпляром точки в имени файла, не считая пути. Вероятно, никто не хочет лишать расширения таким способом.
Frotz
0

Две проблемы с вашим кодом:

  1. Вы использовали '(тик) вместо `(тик назад), чтобы окружить команды, которые генерируют строку, которую вы хотите сохранить в переменной.
  2. Вы не "выводили" переменную "$ filename" в канал в команду "cut".

Я бы изменил ваш код на "name =` echo $ filename | cut -f 1 -d '.' `", как показано ниже (опять же, обратите внимание на обратные галочки, окружающие определение переменной name):

$> filename=foo.txt
$> echo $filename
foo.txt
$> name=`echo $filename | cut -f1 -d'.'`
$> echo $name
foo
$> 
FanDeLaU
источник