Extrair base de arquivo sem caminho e extensão no bash

Dado nomes de arquivos como estes:

/the/path/foo.txt bar.txt 

Espero conseguir:

 foo bar 

Por que isso não funciona?

 #!/bin/bash fullfile=$1 fname=$(basename $fullfile) fbname=${fname%.*} echo $fbname 

Qual é o caminho certo para fazer isso?

Você não precisa chamar o comando basename externo. Em vez disso, você poderia usar os seguintes comandos:

 $ s=/the/path/foo.txt $ echo ${s##*/} foo.txt $ s=${s##*/} $ echo ${s%.txt} foo $ echo ${s%.*} foo 

Observe que essa solução deve funcionar em todos os shells compatíveis com POSIX recentes ( pós 2004 ) (por exemplo, bash , dash , ksh , etc.).

Fonte: Linguagem de Comando Shell 2.6.2 Expansão de parameters

Mais sobre bash String Manipulations: http://tldp.org/LDP/LG/issue18/bash.html

O comando basename tem duas invocações diferentes; em um, você especifica apenas o caminho, em cujo caso ele fornece o último componente, enquanto no outro você também dá um sufixo que será removido. Assim, você pode simplificar seu código de exemplo usando a segunda chamada de basename. Além disso, tenha cuidado para citar corretamente as coisas:

 fbname = $ (basename "$ 1" .txt)
 echo "$ fbname"

Uma combinação de base e corte funciona bem, mesmo no caso de finalizações duplas como .tar.gz :

 fbname=$(basename "$fullfile" | cut -d. -f1) 

Seria interessante se esta solução necessitasse de menos energia aritmética do que a Expansão de parameters Bash.

bash pura, sem basename , sem malabarismo variável. Definir uma string e echo :

 s=/the/path/foo.txt echo ${s//+(*\/|.*)} 

Saída:

 foo 

Nota: a opção bash extglob deve estar “on”, (no Ubuntu ela está “on” por padrão), se não estiver, faça:

 shopt -s extglob 

Andando pelo ${s//+(*\/|.*)} :

  1. ${s – comece com $ s .
  2. // substitui todas as instâncias do padrão.
  3. +( corresponde a um ou mais da lista de padrões entre parênteses.
  4. *\/ corresponde a qualquer coisa antes de / . (1º padrão)
  5. | Ou. (separador de padrões)
  6. .* corresponde a qualquer coisa depois . . (2º padrão)
  7. ) lista de padrões final.
  8. } expansão do parâmetro final – já que não há / (que precederia um substituto de string), os padrões correspondentes são excluídos.

Fundo de man bash relevante:

  1. substituição de padrões :
  ${parameter/pattern/string} Pattern substitution. The pattern is expanded to produce a pat‐ tern just as in pathname expansion. Parameter is expanded and the longest match of pattern against its value is replaced with string. If pattern begins with /, all matches of pattern are replaced with string. Normally only the first match is replaced. If pattern begins with #, it must match at the begin‐ ning of the expanded value of parameter. If pattern begins with %, it must match at the end of the expanded value of parameter. If string is null, matches of pattern are deleted and the / fol‐ lowing pattern may be omitted. If parameter is @ or *, the sub‐ stitution operation is applied to each positional parameter in turn, and the expansion is the resultant list. If parameter is an array variable subscripted with @ or *, the substitution operation is applied to each member of the array in turn, and the expansion is the resultant list. 
  1. correspondência estendida de padrões :
  If the extglob shell option is enabled using the shopt builtin, several extended pattern matching operators are recognized. In the following description, a pattern-list is a list of one or more patterns separated by a |. Composite patterns may be formed using one or more of the fol‐ lowing sub-patterns: ?(pattern-list) Matches zero or one occurrence of the given patterns *(pattern-list) Matches zero or more occurrences of the given patterns +(pattern-list) Matches one or more occurrences of the given patterns @(pattern-list) Matches one of the given patterns !(pattern-list) Matches anything except one of the given patterns 

Aqui estão os oneliners:

  1. $(basename ${s%.*})
  2. $(basename ${s} .${s##*.})

Eu precisava disso, o mesmo que o pedido de bongbang e w4etwetewtwet.

Aqui está outra maneira (mais complexa) de obter o nome do arquivo ou extensão, primeiro use o comando rev para inverter o caminho do arquivo, cortado do primeiro . e depois inverta o caminho do arquivo novamente, assim:

 filename=`rev <<< "$1" | cut -d"." -f2- | rev` fileext=`rev <<< "$1" | cut -d"." -f1 | rev` 

Se você quer jogar bem com os caminhos de arquivos do Windows (no Cygwin), você também pode tentar isto:

 fname=${fullfile##*[/|\\]} 

Isso será responsável pelos separadores de barra invertida ao usar o BaSH no Windows.

Apenas uma alternativa que eu criei para extrair uma extensão, usando os posts desta thread com minha pequena base de conhecimento que era mais familiar para mim.

 ext="$(rev <<< "$(cut -f "1" -d "." <<< "$(rev <<< "file.docx")")")" 

Nota: Por favor, informe sobre o meu uso de citações; funcionou para mim, mas eu poderia estar perdendo algo em seu uso adequado (eu provavelmente uso demais).

Use o comando basename. Sua manpage está aqui: http://unixhelp.ed.ac.uk/CGI/man-cgi?basename