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

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

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

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

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

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


person mimicocotopus    schedule 28.08.2012    source источник
comment
basename $filename .exe сделал бы то же самое. Это предполагает, что вы всегда знаете, какое расширение хотите удалить.   -  person mpe    schedule 28.08.2012
comment
@mpe, ты имеешь в виду basename "$filename" .exe. В противном случае имена файлов с пробелами были бы плохой новостью.   -  person Charles Duffy    schedule 15.06.2016


Ответы (14)


Вы должны использовать синтаксис подстановки команд $(command) при хотите выполнить команду в script/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
person Rohan    schedule 28.08.2012
comment
Спасибо. Я также заметил, что мне нужно использовать команду echo. имя=echo $filename | cut -f1 -d'.' - person mimicocotopus; 28.08.2012
comment
Обратные кавычки не рекомендуются POSIX, предпочтительнее $(). - person jordanm; 28.08.2012
comment
Разветвление и конвейер, чтобы получить несколько символов, — это худшее решение, которое можно себе представить. - person Jens; 15.09.2015
comment
Проблема с этим ответом заключается в том, что он предполагает, что входная строка имеет ТОЛЬКО одну точку... @chepner ниже имеет гораздо лучшее решение... name=${filename%.*} - person Scott Stensland; 30.04.2016
comment
Этот ответ характерен для новичков и не должен распространяться. Используйте встроенный механизм, как описано в ответе Чепнера. - person neric; 24.04.2017
comment
Сбой на ./ihavenoextension, как и в другом ответе. - person jpmc26; 14.09.2019
comment
будьте очень осторожны, если хотите использовать более одной точки, например ./text.log - person Y00; 18.10.2019
comment
Спасибо, я искал некоторое время для этого. Я использовал его, чтобы обрезать последний октет IP-адреса echo 192.168.5.123 | вырезать -f -3 -d '.' - person nassim; 27.03.2020
comment
это не работает, если имя файла a.b.c.txt' - person Simon Jinyu Liu; 09.01.2021
comment
Это охватывает все возможности: stackoverflow.com/a/66177670/12969125 - person shaheen g; 14.02.2021

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

$ filename=foo.txt
$ echo "${filename%.*}"
foo

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

  • Если имя файла начинается только с точки (например, .bashrc), оно удалит все имя файла.
  • Если точка есть только в пути (например, path.to/myfile или ./myfile), то она будет обрезана внутри пути.
person chepner    schedule 28.08.2012
comment
здесь объяснение команды: gnu.org/software /bash/manual/html_node/ - person Carlos Robles; 06.07.2016
comment
И здесь я собирался использовать echo -n "This.File.Has.Periods.In.It.txt" | awk -F. '{$NF=""; print $0}' | tr ' ' '.' | rev | cut -c 2- | rev. Спасибо. - person user208145; 31.08.2016
comment
Работает ли это с файлами с несколькими расширениями, такими как image.png.gz? - person Hawker65; 26.04.2018
comment
%.* удалит только последнее расширение; если вы хотите удалить все расширения, используйте %%.*. - person chepner; 26.04.2018
comment
Кажется, это работает лучше, чем принятый ответ. Он удаляет только последнее расширение, если в файле более одной точки, тогда как cut удаляет все после первой точки. - person Dale Anderson; 28.08.2018
comment
Имейте в виду, что это удаляет все имя, если нет расширения и путь относительный. Например, filename=./ihavenoextension. - person jpmc26; 14.09.2019
comment
@Hawker65, если вы знаете, что файл будет иметь расширение .png.gz, вы можете использовать {file%.png.gz}. Это предотвратит обрезку после точек ранее в файле или в пути! - person mwfearnley; 30.09.2020
comment
Это охватывает все возможности: stackoverflow.com/a/66177670/12969125 - person shaheen g; 14.02.2021

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

$ basename /home/jsmith/base.wiki .wiki
base
person Steven Penny    schedule 02.05.2015
comment
Как я могу удалить только .wiki и получить /home/jsmith/base? - person Gabriel Staples; 01.07.2019
comment
Обновление: ответ: см. stackoverflow.com/a/32584935/4561887. Работает отлично! - person Gabriel Staples; 01.07.2019

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

echo $filename | rev | cut -f 2- -d '.' | rev
person manish_s    schedule 15.09.2015
comment
Я забыл средний оборот, но как только я его увидел, это было здорово! - person supreme Pooba; 29.09.2016
comment
Еще лучше, если параметр -s указан для cut, так что он возвращает пустую строку всякий раз, когда имя файла не содержит точки. - person Hibou57; 27.04.2020
comment
На мой взгляд, это должен быть принятый ответ, поскольку он работает с путями с точками в них, со скрытыми файлами, начинающимися с точки, или даже с файлами с несколькими расширениями. - person Tim Krief; 22.06.2020
comment
Что происходит с таким файлом: filename=/tmp.d/foo? - person FedonKadifeli; 13.09.2020
comment
@FedonKadifeli, это работает только для имени файла, а не для полного пути. Но вы можете сначала получить имя файла, используя аналогичный подход, используя / в качестве разделителя. - person manish_s; 15.09.2020
comment
У него также есть проблемы с точечными файлами. Попробуйте с .profile например. - person FedonKadifeli; 15.09.2020
comment
@FedonKadifeli использует что-то вроде этого: basename "$INPUT" | rev | cut -f 2- -d '.' | rev работает отлично. $INPUT — это имя файла или путь. - person donvercety; 25.03.2021

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

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

person Raghwendra    schedule 26.04.2016
comment
Что происходит, когда file1=/tmp.d/mainonetwosh? Выражение sed следует заменить на 's/\.[^./]*$//' - person FedonKadifeli; 13.09.2020

Только с использованием встроенного POSIX:

#!/usr/bin/env sh
path=this.path/with.dots/in.path.name/filename.tar.gz

# Get the basedir without external command
# by stripping out shortest trailing match of / followed by anything
dirname=${path%/*}

# Get the basename without external command
# by stripping out longest leading match of anything followed by /
basename=${path##*/}

# Strip uptmost trailing extension only
# by stripping out shortest trailing match of dot followed by anything
oneextless=${basename%.*}; echo "$noext" 

# Strip all extensions
# by stripping out longest trailing match of dot followed by anything
noext=${basename%%.*}; echo "$noext"

# Printout demo
printf %s\\n "$path" "$dirname" "$basename" "$oneextless" "$noext"

Демонстрация распечатки:

this.path/with.dots/in.path.name/filename.tar.gz
this.path/with.dots/in.path.name
filename.tar.gz
filename.tar
filename
person Community    schedule 19.09.2020

#!/bin/bash
file=/tmp/foo.bar.gz
echo $file ${file%.*}

выходы:

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

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

person C. Paul Bond    schedule 05.06.2018
comment
Что происходит с таким файлом: file=/tmp.d/foo ? - person FedonKadifeli; 13.09.2020

Я рекомендую использовать 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%.*}")'
person Alexey K.    schedule 06.03.2019

#!/bin/bash
filename=program.c
name=$(basename "$filename" .c)
echo "$name"

выходы:

program
person booboo    schedule 28.04.2018
comment
Чем это отличается от ответа, данного Стивеном Пенни 3 года назад? - person gniourf_gniourf; 29.04.2018
comment
@gniourf_gniourf поддерживает голосование, потому что вы сделали его полезным ответом, показав, как правильно использовать его с переменной. - person mwfearnley; 30.09.2020

Как указал Hawker65 в комментарии к ответу chepner, решение с наибольшим количеством голосов не заботится ни о нескольких расширениях (например, имя файла.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)
person MadMage    schedule 30.08.2018
comment
Этот разделяет tar.gz, выбирая символы перед первым экземпляром точки в имени файла, не считая пути. Вероятно, никто не хочет удалять расширения таким образом. - person Frotz; 24.10.2018
comment
Если вы знаете, что он заканчивается на .tar.gz, вы можете просто использовать $(basename "$a" .tar.gz). Кроме того, убедитесь, что ваша переменная везде заключена в кавычки, если есть шанс, что она будет содержать пробелы или другие странные символы! - person mwfearnley; 30.09.2020

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

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

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

$> filename=foo.txt
$> echo $filename
foo.txt
$> name=`echo $filename | cut -f1 -d'.'`
$> echo $name
foo
$> 
person FanDeLaU    schedule 28.07.2016

Ответы, предоставленные ранее, имеют проблемы с путями, содержащими точки. Несколько примеров:

/xyz.dir/file.ext
./file.ext
/a.b.c/x.ddd.txt

Я предпочитаю использовать |sed -e 's/\.[^./]*$//'. Например:

$ echo "/xyz.dir/file.ext" | sed -e 's/\.[^./]*$//'
/xyz.dir/file
$ echo "./file.ext" | sed -e 's/\.[^./]*$//'
./file
$ echo "/a.b.c/x.ddd.txt" | sed -e 's/\.[^./]*$//'
/a.b.c/x.ddd

Примечание. Если вы хотите удалить несколько расширений (как в последнем примере), используйте |sed -e 's/\.[^/]*$//':

$ echo "/a.b.c/x.ddd.txt" | sed -e 's/\.[^/]*$//'
/a.b.c/x

Однако этот метод не работает в точечных файлах без расширения:

$ echo "/a.b.c/.profile" | sed -e 's/\.[^./]*$//'
/a.b.c/

Чтобы покрыть и такие случаи, вы можете использовать:

$ echo "/a.b.c/.profile" | sed -re 's/(^.*[^/])\.[^./]*$/\1/'
/a.b.c/.profile
person FedonKadifeli    schedule 13.09.2020

Этот охватывает все возможности! (точка в пути или нет; с расширением или без расширения):

tmp1=${filename##*/};tmp2=${tmp1:1};filename_noextension=$(echo -n ${tmp1:0:1};echo ${tmp2%.*});echo $filename_noextension

Примечания:

  • Это дает вам имя файла без какого-либо расширения. Таким образом, в переменной $filename_noextension нет пути.
  • В итоге вы получите две нежелательные переменные $tmp1 и $tmp2. Убедитесь, что вы не используете их в своем сценарии.

примеры для проверки:

filename=.bashrc; echo "filename: $filename"; tmp1=${filename##*/};tmp2=${tmp1:1};filename_noextension=$(echo -n ${tmp1:0:1};echo ${tmp2%.*}); echo "filename without extension: $filename_noextension"

filename=.bashrc.txt; echo "filename: $filename"; tmp1=${filename##*/};tmp2=${tmp1:1};filename_noextension=$(echo -n ${tmp1:0:1};echo ${tmp2%.*}); echo "filename without extension: $filename_noextension"

filename=.bashrc.txt.tar; echo "filename: $filename"; tmp1=${filename##*/};tmp2=${tmp1:1};filename_noextension=$(echo -n ${tmp1:0:1};echo ${tmp2%.*}); echo "filename without extension: $filename_noextension"

filename=~/.bashrc; echo "filename: $filename"; tmp1=${filename##*/};tmp2=${tmp1:1};filename_noextension=$(echo -n ${tmp1:0:1};echo ${tmp2%.*}); echo "filename without extension: $filename_noextension"

filename=~/.bashrc.txt.tar; echo "filename: $filename"; tmp1=${filename##*/};tmp2=${tmp1:1};filename_noextension=$(echo -n ${tmp1:0:1};echo ${tmp2%.*}); echo "filename without extension: $filename_noextension"

filename=bashrc; echo "filename: $filename"; tmp1=${filename##*/};tmp2=${tmp1:1};filename_noextension=$(echo -n ${tmp1:0:1};echo ${tmp2%.*}); echo "filename without extension: $filename_noextension"

filename=bashrc.txt; echo "filename: $filename"; tmp1=${filename##*/};tmp2=${tmp1:1};filename_noextension=$(echo -n ${tmp1:0:1};echo ${tmp2%.*}); echo "filename without extension: $filename_noextension"

filename=bashrc.txt.tar; echo "filename: $filename"; tmp1=${filename##*/};tmp2=${tmp1:1};filename_noextension=$(echo -n ${tmp1:0:1};echo ${tmp2%.*}); echo "filename without extension: $filename_noextension"

filename=~/bashrc; echo "filename: $filename"; tmp1=${filename##*/};tmp2=${tmp1:1};filename_noextension=$(echo -n ${tmp1:0:1};echo ${tmp2%.*}); echo "filename without extension: $filename_noextension"

filename=~/bashrc.txt.tar; echo "filename: $filename"; tmp1=${filename##*/};tmp2=${tmp1:1};filename_noextension=$(echo -n ${tmp1:0:1};echo ${tmp2%.*}); echo "filename without extension: $filename_noextension"

person shaheen g    schedule 12.02.2021

введите здесь описание изображения

Предполагая, что ваши файлы имеют расширение .new

лс -1 | awk '{ print mv $1 basename "$1" .new}' | ш

Поскольку специальные цитаты после публикации не отображаются, см. изображение.

person Avadh Joshi    schedule 01.05.2021