Como faço para remover o sufixo do arquivo e a parte do caminho de uma string de caminho no Bash?

396

Dado um caminho de arquivo de string, como /foo/fizzbuzz.bar, como eu usaria o bash para extrair apenas a fizzbuzzparte da referida string?

Lawrence Johnston
fonte
Informações que você pode encontrar no manual do Bash , procurar ${parameter%word}e ${parameter%%word}na seção correspondente à parte à direita.
1ac0

Respostas:

574

Veja como fazer isso com os operadores # e% no Bash.

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

${x%.bar}Também pode ser ${x%.*}remover tudo após um ponto ou ${x%%.*}remover tudo após o primeiro ponto.

Exemplo:

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

A documentação pode ser encontrada no manual do Bash . Procure ${parameter%word}e ${parameter%%word}arraste a seção correspondente da parte.

Zan Lynx
fonte
Acabei usando este porque era o mais flexível e havia algumas outras coisas semelhantes que eu queria fazer, bem como isso.
Lawrence Johnston
Essa é provavelmente a mais flexível de todas as respostas postadas, mas acho que as respostas que sugerem os comandos basename e dirname também merecem atenção. Eles podem ser apenas o truque se você não precisar de nenhuma outra correspondência de padrões sofisticados.
mgadda
7
Como isso se chama $ {x% .bar}? Eu gostaria de aprender mais sobre isso.
Manjericão
14
@ Basil: Expansão de parâmetros. Em um console, digite "man bash" e digite "/ parameter expansion"
Zan Lynx
11
Eu acho que a explicação 'bash man' faz sentido se você já sabe o que faz ou se tentou por si mesmo da maneira mais difícil. É quase tão ruim quanto a referência do git. Eu apenas pesquisaria no Google.
triplebig
226

veja o comando basename:

NAME="$(basename /foo/fizzbuzz.bar .bar)"
zigdon
fonte
11
Provavelmente a mais simples de todas as soluções atualmente oferecidas ... embora eu usasse $ (...) em vez de reticulares.
Michael Johnson
6
Mais simples, mas acrescenta uma dependência (não uma enorme ou estranha, admito). Ele também precisa conhecer o sufixo.
Vinko Vrsalovic 24/09/08
E pode ser usado para remover qualquer coisa do final, basicamente faz apenas uma remoção de string do final.
Smar
2
O problema é o tempo atingido. Acabei de pesquisar a pergunta para esta discussão depois de assistir ao bash levar quase 5 minutos para processar 800 arquivos, usando basename. Usando o método regex acima, o tempo foi reduzido para cerca de 7 segundos. Embora essa resposta seja mais fácil de executar para o programador, o tempo atingido é demais. Imagine uma pasta com alguns milhares de arquivos nela! Eu tenho algumas dessas pastas.
Xizdaqrian
@xizdaqrian Isso é absolutamente falso. Este é um programa simples, que não deve demorar meio segundo para retornar. Acabei de executar o tempo find / home / me / dev -name "* .py" .py -exec basename {} \; e retirou a extensão e o diretório de 1500 arquivos em 1 segundo no total.
Laszlo Treszkai 12/12/19
40

Pure bash, realizado em duas operações separadas:

  1. Remova o caminho de uma string de caminho:

    path=/foo/bar/bim/baz/file.gif
    
    file=${path##*/}  
    #$file is now 'file.gif'
  2. Remova a extensão de uma cadeia de caminho:

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

Usando o nome de base, usei o seguinte para conseguir isso:

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

    # Do things with $fname
done;

Isso não requer conhecimento a priori da extensão do arquivo e funciona mesmo quando você tem um nome de arquivo com pontos no nome do arquivo (na frente da extensão); embora exija o programa basename, mas isso faz parte dos coreutils do GNU, portanto deve ser fornecido com qualquer distribuição.

Mike
fonte
11
Excelente resposta! remove a extensão de uma maneira muito limpa, mas não remove a extensão. no final do nome do arquivo.
Metrix #
3
@ Metrix basta adicionar o "." antes de $ ext, ie: fname=`basename $file .$ext`
Carlos Troncoso 14/05
13

Maneira pura da festança:

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

Esta funcionalidade é explicada no man bash em "Expansão de parâmetros". Maneiras não bash abundam: awk, perl, sed e assim por diante.

EDIT: trabalha com pontos nos sufixos do arquivo e não precisa saber o sufixo (extensão), mas não funciona com pontos no próprio nome .

Vinko Vrsalovic
fonte
11

As funções basename e dirname são o que você procura:

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

Possui saída:

basename: fizzbuzz.bar
basename + remove .bar: fizzbuzz
dirname: /foo
Jerub
fonte
11
Seria útil corrigir a citação aqui - talvez execute isso no shellcheck.net com mystring=$1o valor constante atual (que suprimirá vários avisos, garantindo que não contenha espaços / caracteres globos / etc) e resolva os problemas encontra?
Charles Duffy
Bem, fiz algumas alterações apropriadas para suportar aspas em $ mystring. Puxa isso foi há muito tempo atrás eu escrevi este :)
Jerub
Seria uma melhoria adicional para citar os resultados: echo "basename: $(basename "$mystring")"- dessa forma, se mystring='/foo/*'você não for *substituído por uma lista de arquivos no diretório atual após a basename conclusão.
Charles Duffy
6

Usar basenamesupõe que você saiba qual é a extensão do arquivo, não é?

E acredito que as várias sugestões de expressões regulares não lidam com um nome de arquivo que contém mais de um "."

O seguinte parece lidar com pontos duplos. Ah, e nomes de arquivos que contenham um "/" (apenas para brincadeiras)

Parafraseando Pascal, "Desculpe, este script é tão longo. Não tive tempo para reduzi-lo"


  #!/usr/bin/perl
  $fullname = $ARGV[0];
  ($path,$name) = $fullname =~ /^(.*[^\\]\/)*(.*)$/;
  ($basename,$extension) = $name =~ /^(.*)(\.[^.]*)$/;
  print $basename . "\n";
 
Andrew Edgecombe
fonte
11
Isso é bom e robusto
Gaurav Jain
4

Além da sintaxe em conformidade com POSIX usada nesta resposta ,

basename string [suffix]

como em

basename /foo/fizzbuzz.bar .bar

O GNUbasename suporta outra sintaxe:

basename -s .bar /foo/fizzbuzz.bar

com o mesmo resultado. A diferença e vantagem é que isso -simplica -a, que suporta vários argumentos:

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

Isso pode até tornar o nome do arquivo seguro, separando a saída com bytes NUL usando a -zopção, por exemplo, para esses arquivos que contêm espaços em branco, novas linhas e caracteres glob (citados por ls):

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

Lendo em uma matriz:

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

readarray -drequer o Bash 4.4 ou mais recente. Para versões mais antigas, temos que fazer um loop:

while IFS= read -r -d '' fname; do arr+=("$fname"); done < <(basename -zs .bar has*)
Benjamin W.
fonte
Além disso, o sufixo especificado é removido na saída, se presente (e ignorado em contrário).
precisa saber é o seguinte
3
perl -pe 's/\..*$//;s{^.*/}{}'
mopoke
fonte
3

Se você não pode usar o nome da base como sugerido em outras postagens, sempre pode usar o sed. Aqui está um exemplo (feio). Não é o melhor, mas funciona extraindo a sequência desejada e substituindo a entrada pela sequência desejada.

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

O que lhe dará a saída

fizzbuzz

nymacro
fonte
Embora essa seja a resposta para a pergunta original, esse comando é útil quando tenho linhas de caminhos em um arquivo para extrair nomes de base e imprimi-los na tela.
Sangcheol Choi
2

Cuidado com a solução perl sugerida: ela remove qualquer coisa após o primeiro ponto.

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

Se você quiser fazer isso com perl, isso funciona:

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

Mas se você estiver usando o Bash, as soluções com y=${x%.*}(ou basename "$x" .extse você conhece a extensão) são muito mais simples.

mivk
fonte
1

O nome da base faz isso, remove o caminho. Ele também removerá o sufixo se for fornecido e se corresponder ao sufixo do arquivo, mas você precisará saber o sufixo a ser fornecido ao comando. Caso contrário, você pode usar mv e descobrir qual deve ser o novo nome de outra maneira.

waynecolvin
fonte
1

Combinando a resposta com a melhor classificação com a segunda resposta com a melhor classificação para obter o nome do arquivo sem o caminho completo:

$ x="/foo/fizzbuzz.bar.quux"
$ y=(`basename ${x%%.*}`)
$ echo $y
fizzbuzz
c.gutierrez
fonte
Por que você está usando uma matriz aqui? Além disso, por que usar o nome da base?
codeforester