Como verificar a extensão de um nome de arquivo em um script bash?

170

Estou escrevendo um script de construção noturno no bash.
Tudo está bem e elegante, exceto por um pequeno obstáculo:


#!/bin/bash

for file in "$PATH_TO_SOMEWHERE"; do
      if [ -d $file ]
      then
              # do something directory-ish
      else
              if [ "$file" == "*.txt" ]       #  this is the snag
              then
                     # do something txt-ish
              fi
      fi
done;

Meu problema é determinar a extensão do arquivo e agir em conformidade. Eu sei que o problema está na instrução if, testando um arquivo txt.

Como posso determinar se um arquivo tem um sufixo .txt?

Anthon
fonte
Isso será interrompido se você tiver um arquivo com um espaço em seu nome.
Jfg956
Além da resposta de Paulo, você pode usar $(dirname $PATH_TO_SOMEWHERE)e $(basename $PATH_TO_SOMEWHERE)se dividir em pasta e diretório e fazer algo diretório-ish e arquivo-ish
McPeppr

Respostas:

248

Eu acho que você quer dizer "Os quatro últimos caracteres de $ file são iguais .txt?" Nesse caso, você pode usar o seguinte:

if [ ${file: -4} == ".txt" ]

Observe que o espaço entre file:e -4é necessário, pois o modificador ': -' significa algo diferente.

Paul Stephenson
fonte
1
para esse fim, você também pode renomear command.com para command.txt em uma máquina Windows.
9139 hometoast
9
Se você deseja especificar uma desigualdade, lembre-se de incluir suportes extras: if [[$ {file: -4} = ".txt"]!]
Ram Rajamony
4
@RamRajamony Por que é necessário usar [[ao testar a desigualdade?
precisa saber é o seguinte
Eu queria ressaltar que o espaço após o cólon é importante. ${var:-4}não é o mesmo que ${var: -4}; o primeiro (sem espaço) será expandido como '-4' se var estiver desativado; o segundo (com espaço) retornará os últimos 4 caracteres de var.
Pbatey 19/07/19
1
No bash, isso produzirá um erro "[: ==: operador unário esperado", a menos que você coloque aspas na primeira variável. Então, em if [ "${file: -4}" == ".txt" ]vez disso.
Giles B
264

Faço

if [ "$file" == "*.txt" ]

como isso:

if [[ $file == *.txt ]]

Ou seja, colchetes duplos e sem aspas.

O lado direito ==é um padrão de concha. Se você precisar de uma expressão regular, use-o =~então.


fonte
15
Eu não sabia disso. Parece ser um caso especial em que o lado direito de == ou! = É expandido como um padrão de shell. Pessoalmente, acho que isso é mais claro do que minha resposta.
21139 Paul Stephenson
20
Eu sou novo no bash e demorei um pouco para descobrir como usar isso em uma ifdeclaração multi-condicional . Estou compartilhando aqui, caso ajude alguém. if [[ ( $file == *.csv ) || ( $file == *.png ) ]]
joelostblom
6
@cheflo que é bom para várias condições em geral. Nesse caso específico, você também pode usar if [[ $file =~ .*\.(csv|png) ]]. É mais curto, mais claro, mais fácil de adicionar extensões adicionais e pode ser facilmente configurável (colocando "csv | png" em uma variável).
jox
4
Você pode colocar aspas duplas no arquivo. if [[ "$file" == *.txt ]]Se o arquivo tiver espaços em seu nome, serão necessárias aspas duplas.
shawnhcorey
Devo usar esta resposta em vez da resposta aceita?
Freedo
26

Você simplesmente não pode ter certeza em um sistema Unix, que um arquivo .txt é realmente um arquivo de texto. Sua melhor aposta é usar "arquivo". Talvez tente usar:

file -ib "$file"

Em seguida, você pode usar uma lista de tipos MIME para comparar ou analisar a primeira parte do MIME, onde você obtém itens como "texto", "aplicativo" etc.

Eldelshell
fonte
2
Como file -i...inclui a codificação MIME, você pode usarfile --mime-type -b ...
Wilf
24

Você pode usar o comando "arquivo" se realmente deseja descobrir informações sobre o arquivo, em vez de confiar nas extensões.

Se você se sentir confortável em usar a extensão, poderá usar o grep para verificar se ela corresponde.

Adam Peck
fonte
sim, eu estou ciente do filecomando. Na verdade, eu tentei fazer a correspondência com base na saída do referido comando ... mas falhei horrivelmente nessas declarações if.
17

Você também pode fazer:

   if [ "${FILE##*.}" = "txt" ]; then
       # operation for txt files here
   fi
kvz
fonte
11
case $FILE in *.txt ) ... ;; esacpareceria mais robusto e idiomático.
Tripleee
11

Semelhante ao 'arquivo', use o 'mimetype -b', um pouco mais simples, que funcionará independentemente da extensão do arquivo.

if [ $(mimetype -b "$MyFile") == "text/plain" ]
then
  echo "this is a text file"
fi

Edit: você pode precisar instalar o libfile-mimeinfo-perl no seu sistema se o tipo de tipo não estiver disponível

dargaud
fonte
1
Você deve deixar claro que o script 'mimetype' não está disponível em todos os sistemas.
Gilbertpilz
Feito, adicionado libfile-mimeinfo-perl
dargaud 19/05/19
5

A resposta correta sobre como disponibilizar a extensão em um nome de arquivo no linux é:

${filename##*\.} 

Exemplo de impressão de todas as extensões de arquivo em um diretório

for fname in $(find . -maxdepth 1 -type f) # only regular file in the current dir
    do  echo ${fname##*\.} #print extensions 
done
Albin. Com.
fonte
Sua resposta usa uma barra invertida dupla, mas seu exemplo usa apenas uma única barra invertida. Seu exemplo está correto, sua resposta não.
Gilbertpilz 19/05
3

Escrevi um script bash que analisa o tipo de arquivo e depois o copia para um local. Utilizo-o para examinar os vídeos que assisti online do meu cache do Firefox:

#!/bin/bash
# flvcache script

CACHE=~/.mozilla/firefox/xxxxxxxx.default/Cache
OUTPUTDIR=~/Videos/flvs
MINFILESIZE=2M

for f in `find $CACHE -size +$MINFILESIZE`
do
    a=$(file $f | cut -f2 -d ' ')
    o=$(basename $f)
    if [ "$a" = "Macromedia" ]
        then
            cp "$f" "$OUTPUTDIR/$o"
    fi
done

nautilus  "$OUTPUTDIR"&

Ele usa idéias semelhantes às apresentadas aqui, espero que isso seja útil para alguém.

desdecode
fonte
2

Eu acho que '$PATH_TO_SOMEWHERE'é algo assim '<directory>/*'.

Nesse caso, eu mudaria o código para:

find <directory> -maxdepth 1 -type d -exec ... \;
find <directory> -maxdepth 1 -type f -name "*.txt" -exec ... \;

Se você quiser fazer algo mais complicado com o diretório e os nomes dos arquivos de texto, poderá:

find <directory> -maxdepth 1 -type d | while read dir; do echo $dir; ...; done
find <directory> -maxdepth 1 -type f -name "*.txt" | while read txtfile; do echo $txtfile; ...; done

Se você tiver espaços nos nomes dos arquivos, poderá:

find <directory> -maxdepth 1 -type d | xargs ...
find <directory> -maxdepth 1 -type f -name "*.txt" | xargs ...
jfg956
fonte
Estes são ótimos exemplos de como você faz 'loops' no shell. É melhor reservar explícitos fore whileloops para quando o corpo do loop precisar ser mais complexo.