Como posso operar todos os arquivos de um determinado tipo, se eles não tiverem a extensão correta?

6

Esta questão é motivada por um pequeno script que encontrei em uma revista Linux. Como prova de que não inventei isso, aqui está uma foto:

Eu gostaria de escrever uma carta ao editor desta publicação sobre o que há de errado com isso e como escrevê-lo melhor.

O script tenta capturar arquivos jpeg em uma variável, para que algo (compactação usando lepton ) possa ser feito com eles.

for jpeg in 'echo "$(file $(find ./ ) |
   grep JPEG | cut -f 1 -d ':')"'
  do
     /path/to/command "$jpeg"
...

Aparentemente, neste exemplo, não podemos confiar que os arquivos serão nomeados com uma extensão .jpg , por isso não podemos pegá-los com algo como

for f in *.JPG *.jpg *.JPEG *.jpeg ; do ...

porque o escritor usou file para verificar seu tipo, mas se os nomes dos arquivos não puderem ser confiáveis para ter uma extensão sensata, então não vejo como podemos confiar neles para não serem -rf * ou (; \ $!| ou tem novas linhas ou qualquer outra coisa.

Como eu posso capturar arquivos em uma variável por tipo com for ou while , ou talvez evitar isso usando find com -exec , ou algum outro método?

Bônus para insights e demonstrações do que há de errado com o código na foto.

Eu marquei essa pergunta com [bash] já que é sobre um script bash, mas se você sentir vontade de responder uma maneira de fazer isso que não usa bash, então sinta-se à vontade para fazer isso.

    
por Zanna 26.08.2017 / 11:28

3 respostas

5

Codifique primeiro:

Vamos fazer isso com os globs especiais do Bash e um for loop:

#!/bin/bash
shopt -s globstar dotglob

for f in ./** ; do 
    if file -b -- "$f" | grep -q '^JPEG image data,' ; then 

        # do whatever you want with the JPEG file "$f" in here:
        md5sum -- "$f"

    fi
done

Explicação:

Antes de mais nada, precisamos tornar as BashGans mais úteis ativando as opções globstar e dotglob shell. Aqui está sua descrição de man bash na seção SHELL BUILTIN COMMANDS sobre shopt :

 dotglob 
    If set, bash includes filenames beginning with a '.' in the results of 
    pathname expansion.
 globstar
    If set, the pattern ** used in a pathname expansion context will match 
    all files and zero or more directories and subdirectories. If the pattern
    is followed by a /, only directories and subdirectories match.

Em seguida, usamos esse novo loop "recursive glob" ./** em for para iterar todos os arquivos e pastas dentro do diretório atual e de todos os seus subdiretórios. Por favor, sempre use caminhos absolutos ou caminhos relativos explícitos começando com ./ ou ../ em suas globs, não apenas ** , para evitar problemas com nomes de arquivos especiais como ~ .

Agora, testamos cada nome de arquivo (e pasta) com o comando file para seu conteúdo. A opção -b impede que ele imprima o nome do arquivo novamente antes da sequência de informações do conteúdo, o que torna a filtragem mais segura.

Agora sabemos que as informações de conteúdo de todos os arquivos JPG / JPEG válidos devem começar com JPEG image data, , que é o teste da saída de file com grep . Usamos a opção -q para suprimir qualquer saída, pois estamos interessados somente no código de saída de grep , que indica se o padrão correspondeu ou não.

Se corresponder, o código dentro do bloco if / then será executado. Nós podemos fazer o que quisermos aqui. O nome do arquivo JPEG atual está disponível na variável $f da shell. Nós apenas temos que ter certeza de sempre colocá-lo entre aspas duplas para evitar a avaliação acidental de nomes de arquivos com caracteres especiais, como espaços, novas linhas ou símbolos. Geralmente também é melhor separá-lo de outros argumentos colocando-o após -- , o que faz com que a maioria dos comandos o interprete como um nome de arquivo, mesmo que seja algo como -v ou --help que seria interpretado como uma opção.

Pergunta bônus:

Hora de explodir algum código, para ciência! Aqui está a versão da sua pergunta / livro:

for jpeg in 'echo "$(file $(find ./ ) 
    | grep JPEG | cut -f 1 -d ':')"'
do
     /path/to/command "$jpeg"
done
Primeiro de tudo, permita-me mencionar o quão complexo eles escreveram. Temos 4 níveis de subshells aninhados, usando sintaxes de substituição de comando misto ( '' e $() ), que são apenas necessárias devido ao uso incorreto / sub-ótimo de find .

Aqui find apenas lista todos os arquivos e imprime seus nomes, um por linha. Em seguida, a saída completa é passada para file para examinar cada um deles. Mas espere! Um nome de arquivo por linha? E quanto aos nomes de arquivos que contêm novas linhas? Certo, aqueles vão quebrá-lo!

$ ls --escape ne*ne
new\nline
$ file $(find . -name 'ne*ne' )
./new: cannot open './new' (No such file or directory)
line:  cannot open 'line' (No such file or directory)

Na verdade, mesmo espaços simples também o quebram, porque eles são tratados como separadores também por file . Você não pode nem mesmo citar o "$(find ./ )" aqui como um remédio, porque isso então citaria toda a saída de várias linhas como um único argumento de nome de arquivo.

$ ls simple*
simple spaces.jpg
$ file $(find ./ -name 'simple*')
./simple:   cannot open './simple' (No such file or directory)
spaces.jpg: cannot open 'spaces.jpg' (No such file or directory)

Na próxima etapa, a saída file será verificada com grep JPEG . Você não acha que é um pouco fácil enganar um padrão tão simples, especialmente porque a saída do% normal co_de% sempre contém o nome do arquivo também? Basicamente tudo com "JPEG" em seu nome de arquivo irá desencadear uma correspondência, não importa o que ela contenha.

$ echo "to be or not to be" > IAmNoJPEG.txt
$ file IAmNoJPEG.txt | grep JPEG
IAmNoJPEG.txt: ASCII text

Ok, temos a saída file de todos os arquivos JPEG (ou os que fingem ser um), agora eles processam todas as linhas com file para extrair o nome do arquivo original da primeira coluna, separadas por um dois pontos ... Adivinhe, vamos tentar isso em um arquivo com dois pontos em seu nome:

$ ls colon*
colons:evil.jpeg
$ file colon* | grep JPEG | cut -f 1 -d ':'
colons

Portanto, para concluir, a abordagem do seu livro funciona, mas somente se todos os arquivos verificados não contiverem espaços, novas linhas, dois-pontos e provavelmente outros caracteres especiais e não contiverem a cadeia "JPEG" em nenhum dos nomes de arquivo. Também é meio feio, mas como a beleza está nos olhos de quem vê, não vou falar sobre isso.

    
por Byte Commander 26.08.2017 / 12:59
6

0. O script quer fazer algo assim.

O script mostrado na sua pergunta tenta enumerar os arquivos e verificar se eles são JPEGs, mas não de forma confiável. Ele tenta passar todos os caminhos para file em uma única execução e extrai os nomes e tipos de arquivos da saída de file , que é razoável , pois pode ser mais rápido do que executar file novamente para cada arquivo. Mas para fazer isso corretamente, você precisa ter cuidado sobre como os caminhos são passados para file , como file delimita sua saída e como você consome essa saída. Você pode usar isto:

#!/bin/bash

find . -exec file --mime-type -r0F '' {} + | while read -rd ''; do
    read -r mimetype
    case "$mimetype" in image/jpeg)
        # Bash placed the filename in "$REPLY" -- put commands that use it here.
        # You can have as many commands as you want before the closing ";;" token.
        ;;
    esac
done

Essa é uma das várias maneiras corretas. (Ele não precisa definir IFS= ; veja abaixo.)% Co_de% com find passa vários argumentos de caminho para + e só o executa quantas vezes forem necessárias para processar todos eles , geralmente apenas uma vez. O crédito vai para AFSHIN para a idéia de passando file para --mime-type para obter o tipo MIME, que contém as informações que você realmente deseja e é fácil de analisar.

Uma explicação detalhada segue. Eu usei a tarefa específica de compactação JPEG como exemplo. Isso é o que o script que você mostrou é, e file tem algumas esquisitices que devem ser consideradas ao decidir como melhorar esse script. Se você quiser apenas ver um script que execute lepton em cada arquivo JPEG, você pode pular para a seção 7. Juntando tudo .

  

O termo caminho tem várias definições. Nessa resposta, eu uso para significar nome do caminho .

1. Instalando lepton

O script que você mostrou se destina a percorrer uma hierarquia de diretórios, localizar imagens JPEG e processá-las com o compressor JPEG sem perdas lepton . Para a motivação principal da sua pergunta, o comando pode não ser realmente importante, mas comandos diferentes têm uma sintaxe diferente. Alguns comandos aceitam vários nomes de arquivos de entrada para uma única execução. A maioria aceita lepton para indicar o final das opções. Usarei -- como meu exemplo. O comando lepton não aceita vários nomes de arquivos de entrada e não reconhece lepton .

Para usar -- , instale-o primeiro. É empacotado oficialmente para o Ubuntu 17.04 e posterior ( lepton ). Para lançamentos anteriores do Ubuntu, ou para usar uma versão mais recente do que a que foi lançada para o seu lançamento, clone seu repositório sudo apt install lepton ( git ) e construa a fonte conforme instruído no README . Ou você pode ser capaz de encontre um PPA .

Dependendo de como você o instala, git clone https://github.com/dropbox/lepton.git pode estar em lepton , /usr/bin ou em outro lugar. Provavelmente você vai querer em algum lugar em /usr/local/bin ; então você pode executá-lo como $PATH . O script que você mostrou usa caminhos absolutos para lepton e os utilitários padrão lepton e mv , mas não para os outros utilitários padrão rm , file , find e grep . (Este é o Bash, então cut - sem esse script de qualquer maneira - é um shell embutido . echo é sempre construídos .) Embora essa não seja uma das falhas sérias do script, não há razão discernível para tal inconsistência. A menos que você esteja escrevendo um script para tolerar que não tenha exit definido de maneira sensata - nesse caso, você deve usar caminhos absolutos para todos os comandos externos - sugiro usar caminhos relativos para comandos padrão e aqueles que você instalou.

2. Executando $PATH

Cuidados e informações gerais

Eu testei com o lepton v1.0-1.2.1-104-g209463a (do Git). lepton foi lançado em julho de 2016 então eu acho que a sintaxe atual continuará funcionando Mas versões futuras podem adicionar recursos. Se você estiver lendo este ano, poderá verificar se lepton adicionou suporte a tarefas que antes exigiam scripts.

Por favor, tenha cuidado com os argumentos da linha de comando que você passa . Por exemplo, tentei executar lepton com lepton como o primeiro argumento e -verbose como o segundo. Ele interpretou art.jpg como um nome de arquivo de entrada e encerrou com um erro, mas não antes de truncar -verbose - que interpretou como um nome de arquivo de saída - para baixo para zero bytes. Felizmente eu tive um backup!

Você pode passar zero, um ou dois caminhos para art.jpg . Em todos os casos, ele examina seu arquivo de entrada ou fluxo para ver se ele contém dados JPEG ou Lepton. JPEG é compactado para Lepton; Lepton é descomprimido para JPEG. lepton removerá e adicionará extensões de arquivo, mas não as usará para decidir o que fazer.

Nomes de arquivo zero - lepton lê de stdin e escreve para stdout .

Assim, lepton - é uma maneira de ler lepton - < infile > outfile e gravar em infile , mesmo que seus nomes comecem com outfile (como as opções ). Mas o método que vou usar passa por caminhos que começam com - , então não vou ter que me preocupar com isso.

Um nome de arquivo - . lepton infile e nomeia seu próprio arquivo de saída.

É assim que o script que você mostrou usa infile .

Se o conteúdo de lepton se parecer com um JPEG, infile gerará um arquivo Lepton; se seu conteúdo se parece com um arquivo Lepton, lepton produz um JPEG. lepton decide como deseja nomear seu arquivo de saída removendo uma extensão de lepton , se houver, e adicionando uma extensão infile ou .jpg dependendo do tipo de arquivo está criando. Mas ele não usa a extensão que está removendo (se houver) para inferir o tipo de arquivo em que está operando.

Considera o último .lep e qualquer coisa depois dele como uma extensão. Se . for infile , você receberá a.b.c ou a.b.lep . Se o nome do arquivo começar com a.b.jpg sem nenhum outro . s, . ainda considera isso como uma extensão: de um JPEG chamado lepton , você obtém .abc . Apenas .lep no nome do arquivo - não nos nomes dos diretórios - aciona isso, portanto, de um arquivo Lepton . você obtém x/fo.o/abc (que você quer), não x/fo.o/abc.jpg (o que seria ruim).

Se o nome do arquivo de saída obtido dessa forma nomear um arquivo existente, x/fo.jpg s serão adicionados ao final, após a extensão, até que não seja adicionado, e o nome com sublinhados adicionados será usado: _ , abc.lep , abc.lep_ , etc, abc.lep__ , xyz.jpg , xyz.jpg_ , etc.

Isso funciona melhor quando seus arquivos são nomeados de uma maneira sensata.

Remover e adicionar extensões automaticamente e adicionar sublinhados evita um problema que você teria de gerenciar, evitando a perda de dados quando o arquivo de saída já existir. Mas também expõe o que pode ser uma profunda falha de design no script que você mostrou. Se os seus arquivos são nomeados de forma sensata, então todos os seus arquivos JPEG terminam em xyz.jpg__ ou .jpg (talvez em maiúsculas), e nenhum arquivo não-JPEG é assim chamado. Mas você não precisa examinar os arquivos com .jpeg para descobrir quais são JPEGs!

Assim, a premissa do script que você mostrou é que os arquivos podem não ser nomeados de forma razoável. É sempre ruim para um script se comportar errado ou inesperadamente em nomes de arquivos contendo espaços, file e outros caracteres especiais. Portanto, seu comportamento de dividir em espaços em branco e expandir globs (a substituição de comando externa sem aspas, destinada apenas a dividir nomes de arquivos separados, faz isso) é especialmente ruim. Veja a excelente resposta do Byte Commander para detalhes. Esta é provavelmente a pior falha no script que você mostrou.

Mas também vale a pena considerar o que acontece com nomes de arquivos cujo último * não conceitualmente inicia uma extensão de arquivo. Suponha que . tenha quatro arquivos, todos JPEGs: Pictures , 01. Milan wide-angle sunset , 01. Milan wide-angle sunset highres e 02. Kyle birthday party prep - blooper cakes . Então, 03. The subtle found art of unopened expired paint cans with peeling labels cria for f in ~/Pictures/0*; do lepton "$f"; done , 01.lep , 01.lep_ e 02.lep - provavelmente não é o que você deseja.

Se você tiver JPEGs não denominados 03.lep ou talvez .jpg , a melhor abordagem geral é renomeá-los dessa maneira e investigar quaisquer conflitos de nomenclatura que surgirem durante esse processo. Mas isso está além do escopo desta resposta.

Aqueles problemas de renomeação acontecem com JPEGs não nomeados como JPEGs, não não-JPEGs nomeados como JPEGs. Mesmo assim, pode haver uma solução melhor. Se o problema for .jpeg de arquivos do macOS e você não quiser excluí-los, apenas exclua arquivos com um ._ (ou mesmo um ._ ) inicial.Ainda assim, passar apenas um caminho para . evita a perda de dados (devido às suas regras de lepton de anexação); Se a meta principal for excluir não-JPEGs, a idéia básica é sólida, mesmo que a implementação precise ser corrigida.

Então, usarei a sintaxe _ de um caminho . Mas qualquer um que considere a automação de lepton infile como essa em arquivos com nomes estranhos deve lembrar que os arquivos lepton gerados podem ser nomeados de maneiras que não revelam os nomes dos arquivos de entrada.

Dois nomes de arquivos - .lep faz exatamente o que você espera.

Mas só porque você espera que isso não seja a coisa certa a fazer.

Como nas outras formas de executar lepton infile outfile , lepton determina se lepton é um JPEG a ser compactado ou um arquivo Lepton a ser descompactado examinando seu conteúdo. Se infile for um JPEG, infile gravará um arquivo Lepton chamado lepton ; Se outfile for um arquivo Lepton, infile gravará um arquivo JPEG chamado lepton . Com essa sintaxe de dois caminhos, outfile não altera o nome do arquivo de saída especificado de nenhuma maneira. Não adiciona ou remove extensões nem adiciona lepton s para resolver conflitos de nomenclatura. Se _ já existir, ele será sobrescrito.

Você pode querer isso, mas se não, e você usar esta sintaxe, você terá que resolver o problema fazendo seu script ajustar os nomes dos arquivos de saída. Você pode ser capaz de fazer isso de uma maneira que lhe sirva melhor do que o esquema do próprio outfile quando executado com apenas um argumento de caminho. Mas não tentarei adivinhar suas necessidades e preferências específicas; Vou usar apenas a sintaxe de um caminho.

3. Passando Múltiplos Caminhos De lepton para find

O script que você mostrou tenta usar file para passar um caminho por argumento para file $(find ./ ) executando file em substituição de comandos . Isso geralmente não funciona, porque find divide no espaço em branco, que os nomes de arquivo podem conter. É comum que arquivos - especialmente imagens! - e pastas tenham espaços em seus nomes. O script que você mostrou trata um caminho $(find ./ ) como dois caminhos, ./abc/foo bar.jpg e ./abc/foo . Na melhor das hipóteses, não existe; se o fizerem, você involuntariamente opera na coisa errada. E o caminho original não será processado de todo.

Embora a amplitude desse problema possa ser reduzida pela definição de bar.jpg , então divisão de palavras é realizado apenas entre linhas ( IFS=$'\n' representa um caracter newline ), essa não é uma boa solução . Além de ser estranho, ainda pode falhar, pois os nomes de arquivos e diretórios podem conter novas linhas. Eu aconselho contra a nomeação de arquivos ou diretórios com eles, exceto para testar programas ou scripts de bugs. Mas esses nomes podem ser criados, incluindo por acaso onde você não os espera. Os únicos caracteres que um nome de arquivo não pode conter são o separador de caminho \n e o caractere nulo . O caractere nulo é, portanto, o único que não pode aparecer em um caminho e a única opção segura para delimitar listas de caminhos arbitrários. É por isso que / tem uma ação find e -print0 tem uma opção xargs .

Isso pode ser feito corretamente com -0 , mas você não precisa de um terceiro utilitário para passar caminhos de find . -print0 | xargs -0 ... para find . file ' find action é suficiente. Argumentos após -exec criam o comando para execução, até -exec ou \; . + executa um comando uma vez por arquivo, enquanto find ... -exec ... \; passa o comando como muitos caminhos possíveis por execução, o que geralmente é mais rápido. Normalmente, todos os argumentos se encaixam e o comando é executado apenas uma vez. Em casos raros, a linha de comando seria muito longa e find ... -exec ... + executará o comando mais de uma vez. Portanto, a forma find é segura apenas para executar comandos que (a) executam seus argumentos de caminho no final e (b) funcionam da mesma maneira em uma execução com vários nomes de arquivos como fazem em corridas separadas.

+ é um exemplo de um comando que não deve ser executado usando o lepton form de + porque ele não aceita vários nomes de arquivos de origem. A primeira seria a entrada, a segunda seria a saída e outras seriam excessivas. Mas muitos comandos do fazem a mesma coisa quando são executados uma vez com vários argumentos, como quando são executados várias vezes com um argumento, e -exec é um deles .

Este comando irá gerar a tabela:

find . -exec file --mime-type -r0F '' {} +

file substitui o argumento find por um caminho quando invoca {} e substitui file pelo número de argumentos de caminho adicionais que couberem.

As opções + passadas para --mime-type -r0F '' são explicadas abaixo.

Algumas pessoas citação find , por exemplo, {} . Não há problema em fazê-lo , mas nem Bash nem outros shells estilo Bourne exigem isso. Bash e alguns outros shells suportam expansão de brace , mas um par vazio de chaves não é expandido. Eu escolho não para citar '{}' , à luz do equívoco que citando {} impede que {} execute divisão de palavras . Mesmo que seu shell tenha exigido find a ser citado, isso ainda não teria nada a ver com a divisão de palavras, porque {} nunca faz isso (se você quisesse a divisão de palavras, teria que informar find to find a shell). co_de% não sabe dizer se você escreveu -exec ou find - o shell transforma {} em '{}' (durante quote remoção ) antes de passá-la para '{}' .

4. Emitindo uma tabela ⟨Path, File Type⟩ utilizável com {}

O problema

O motivo pelo qual devo passar algumas opções para find - e não posso usar apenas file - é que a tabela file gerada por padrão é ambígua:

01. Milan wide-angle sunset:                  JPEG image data, JFIF standard 1.01, resolution (DPI), density 1x1, segment length 16, baseline, precision 8, 1400x1400, frames 3
02. Kyle birthday party prep - blooper cakes: JPEG image data, JFIF standard 1.01, aspect ratio, density 1x1, segment length 16, baseline, precision 8, 512x512, frames 3
first line
second line:                       JPEG image data, JFIF standard 1.01, aspect ratio, density 1x1, segment length 16, baseline, precision 8, 500x500, frames 3

Essas três linhas parecem quatro; um nome de arquivo contém uma nova linha. Os nomes de arquivo também podem conter dois pontos, portanto, nem sempre ficará claro onde o nome do arquivo termina. Exemplos mais confusos do que os mostrados acima são possíveis.

A coluna de descrição também tem muito mais informações do que precisamos. Byte Commander explica uma razão find . -exec file {} + ing para file em cada linha inteira retorna resultados errados: um arquivo não-JPEG com grep em seu nome dá um falso positivo. (O ponto de verificar o tipo é que você não pode confiar no nome, então isso é um erro bastante autodestrutivo no script que você mostrou.) Mas mesmo quando você sabe que está procurando na coluna de descrição, pode ainda contém JPEG , mesmo que não seja o tipo:

$ touch empty.JPEG  # not a JPEG
$ gzip -k empty.JPEG
$ file empty.JPEG*
empty.JPEG:    empty
empty.JPEG.gz: gzip compressed data, was "empty.JPEG", last modified: Mon Aug 28 16:37:56 2017, from Unix

A resposta do Byte Commander resolveu isso (a) passando a opção JPEG para JPEG , fazendo com que omita os caminhos, -b separador e espaços na frente do tipo, então (b) usando file para verificar se a descrição começa em> com : (a grep âncora no padrão JPEG faz isso). Isso funciona se você acompanhar os caminhos passados para ^ - não é um problema para o método Byte Commander, que executou ^JPEG image data, separadamente para cada caminho, de qualquer maneira.

A solução

Eu preciso usar uma solução diferente, porque meu objetivo é analisar os caminhos e tipos da saída de file , para que file não precise ser executado separadamente para cada arquivo. Felizmente, o file no Ubuntu tem muitas opções . Eu uso file :

  • file imprime um tipo MIME em vez de uma descrição detalhada. Isso é tudo que eu preciso, e então posso apenas fazer uma correspondência exata contra a coisa toda. Para um JPEG, file --mime-type -r0F '' paths mostra --mime-type na coluna de descrição. (Veja também resposta da AFSHIN .)
  • De acordo com file --mime-type , image/jpeg faz com que caracteres não imprimíveis não sejam para ser substituído por escapes octal como man file . Acredito que, caso contrário, precisaria adicionar uma etapa para converter essas sequências de volta para os caracteres reais, o que provavelmente não pode ser feito de forma confiável - e se essa sequência aparecer literalmente em um nome de arquivo? ( -r não escapam file3 como \ .) Digo "acredito" porque não consegui obter \ para imprimir essa sequência de escape, e Não tenho certeza se realmente faz isso na coluna do nome do arquivo. De qualquer forma, file está seguro aqui.
  • -r é a opção chave aqui. Sem ela, esse método não funcionaria de maneira confiável. Faz com que -0 imprima um caractere nulo - o caractere que nunca é permitido em caminhos, porque é normalmente usado para marcar as extremidades de strings em programas em C - imediatamente após o nome do arquivo. Isso marca a quebra, em cada linha, entre as duas colunas da tabela.
  • file faz -F '' imprimir nada ( file é um argumento vazio) em vez de '' . O cólon não é confiável (pode aparecer em nomes de arquivo) e não tem nenhum benefício aqui, pois um caractere nulo já está sendo impresso para indicar o final da coluna de caminho e o início da coluna de descrição.

Para tornar : run find , uso file --mime-type -r0F '' paths . A ação -exec file --mime-type -r0F '' {} + de find substitui -exec pelos caminhos.

5. Consumindo a Mesa

Eu criei a tabela dessa maneira:

find . -exec file --mime-type -r0F '' {} +

Como detalhado acima, isso coloca um caractere nulo após cada caminho. Seria útil se a descrição também fosse terminada em null, mas {} + não faria isso - a descrição sempre termina com uma nova linha. Portanto, devo ler alternadamente até um caractere nulo, depois presumir que há mais texto e lê-lo até uma nova linha.Eu devo fazer isso para cada arquivo e parar quando nada for deixado.

Lendo cada linha

Essa combinação - leia o texto que pode conter uma nova linha até um caractere nulo, depois leia o texto que não pode conter uma nova linha até uma nova linha - não é como os utilitários comuns do Unix são normalmente usados. A abordagem que vou tomar é canalizar a saída de file para um loop. Cada iteração do loop lê uma única linha da tabela usando o find shell embutido duas vezes, com diferentes opções.

Para ler o caminho , eu uso:

read -rd ''
  • read is -r é a única opção padrão e você deve quase sempre usá-la. Sem ele, as barras invertidas escapam como read da entrada e são traduzidas nos caracteres que elas representam. Nós não queremos isso.
  • Normalmente, \n lê até ver uma nova linha. Para ignorar novas linhas e parar em um caractere nulo, eu uso a opção read , que o Bash fornece, para especificar um caractere diferente. Para um caractere nulo, passe o argumento vazio -d .
  • Eu já estou usando uma extensão Bash (a opção '' ), então também posso usar o comportamento padrão do Bash quando nenhum nome de variável for passado para -d . Ele coloca tudo o que lê - exceto o caractere de término - na variável especial read . Normalmente $REPLY retira espaço em branco ( read caracteres) do início e do final da entrada, e é comum escrever $IFS para evitar isso. Ao ler implicitamente para IFS= read ... no Bash, isso não é necessário.

Para ler a descrição , eu uso:

read -r mimetype
  • Nenhuma barra invertida deve aparecer no tipo MIME, mas é recomendável passar $REPLY para -r , a menos que deseje read escapes translated.
  • Desta vez, eu am especificando explicitamente o nome de uma variável. Chame do que você gosta. Eu escolhi \ .
  • Desta vez, a ausência de mimetype para evitar que o espaço em branco inicial e final seja removido é significativo. Eu quero isso removido. Isso elimina os espaços desde o início da descrição que IFS= grava para tornar a tabela mais legível quando é mostrada em um terminal.

Compondo o Loop

O loop deve continuar enquanto houver outro caminho a ser lido. O comando find retorna verdadeiro (na programação shell isso é zero, ao contrário de quase todas as outras linguagens de programação) quando lê algo com sucesso, e falso (na programação shell, qualquer valor diferente de zero) quando não o faz. Portanto, o idioma comum read é útil aqui. Eu pipe ( while read ) a saída de | - que é a saída de um ou (raramente) mais find comandos - para o loop file .

find . -exec file --mime-type -r0F '' {} + | while read -rd ''; do
    read -r mimetype
    # Commands using "$REPLY" and "$mimetype" go here.
done

Dentro do loop, eu leio o resto da linha para obter a descrição ( while ). Eu não me incomodo em verificar se isso foi bem sucedido. read -r mimetype só deve produzir linhas completas mesmo que encontre erros . ( file envia mensagens de erro e aviso para o erro padrão , então elas não aparecerá no pipeline para corromper a tabela.) Você deve poder confiar nisto.

Se você quiser verificar se file foi bem-sucedido, use read -r mimetype . Ou você pode incluí-lo na condição if do loop:

find . -exec file --mime-type -r0F '' {} + |
while read -rd '' && read -r mimetype; do
    # Commands using "$REPLY" and "$mimetype" go here.
done

Você pode ver que eu também divido a linha superior para facilitar a leitura. (Não é necessário while para dividir em \ .)

Testando o loop

Se você quiser testar o loop antes de prosseguir, pode colocar este comando em (ou em vez de) o comentário | :

    printf '[%s] [%s]\n\n' "$REPLY" "$mimetype"

A saída do loop é algo como isso, dependendo do que você tem no diretório (e deixei de lado a maioria das entradas, por questões de brevidade):

[.] [inode/directory]

[./stuv] [inode/x-empty]

[./ghi
jkl] [inode/x-empty]

[./fo.o/abc
def   ] [image/jpeg]

[./fo.o/wyz.lep] [application/octet-stream]

[./fo.o/wyz] [image/jpeg]

Isso é apenas para ver se o loop funciona corretamente. Colocar as entradas da tabela em # Commands... [ desta forma não ajudaria o script a fazer o que ele precisa fazer, pois os caminhos podem conter ] , [ e novas linhas consecutivas.

6. Usando o caminho extraído e o tipo de arquivo

Em cada iteração do loop, ] contém o caminho e "$REPLY" contém a descrição do tipo. Para descobrir se "$mimetype" nomeia um arquivo JPEG, verifique se "$REPLY" é exatamente "$mimetype" .

Você pode comparar strings usando image/jpeg e if / [ (ou test ) com [[ . Mas eu prefiro = :

find -exec file --mime-type -r0F '' {} + | while read -rd ''; do
    read -r mimetype
    case "$mimetype" in image/jpeg)
        # Put commands here that use "$REPLY".
        ;;
    esac
done

Se você quisesse apenas mostrar os caminhos dos JPEGs no mesmo formato acima - para ajudar a testar caminhos com novas linhas - a declaração case ... case inteira poderia ser:

    case "$mimetype" in image/jpeg) printf '[%s]\n\n' "$REPLY";; esac

Mas o objetivo é executar esac em cada arquivo JPEG. Para fazer isso, use:

    case "$mimetype" in image/jpeg) lepton "$REPLY";; esac

7. Juntando Tudo

Adicionando o comando lepton e uma linha hashbang para executá-lo com o Bash, aqui está o script completo :

#!/bin/bash

find . -exec file --mime-type -r0F '' {} + | while read -rd ''; do
    read -r mimetype
    case "$mimetype" in image/jpeg) lepton "$REPLY";; esac
done

lepton informa o que está fazendo, mas não mostra nomes de arquivo. Este script alternativo imprime uma mensagem com cada caminho antes de executar lepton :

#!/bin/bash

find . -exec file --mime-type -r0F '' {} + | while read -rd ''; do
    read -r mimetype
    case "$mimetype" in image/jpeg)
        printf '\nProcessing "%s":\n' "$REPLY" >&2
        lepton "$REPLY"
    esac
done

Eu imprimi as mensagens para erro padrão ( lepton ), já que é onde >&2 envia suas próprias mensagens. Dessa forma, a saída permanece toda unida quando canalizada ou redirecionada. Executar esse script produz uma saída como essa (mas mais se você tiver mais de dois JPEGs):

Processing "./art.jpg":
lepton v1.0-1.2.1-104-g209463a
6777856 bytes needed to decompress this file
56363 86007
65.53%
2635854 bytes needed to decompress this file
56363 86007
65.53%

Processing "./fo.o/abc
def   ":
lepton v1.0-1.2.1-104-g209463a
6643508 bytes needed to decompress this file
36332 46875
77.51%
2456117 bytes needed to decompress this file
36332 46875
77.51%

A repetição em cada sub-rotina - que também aparece quando você executa lepton sem imprimir nomes de arquivos - é porque lepton verifica se seus arquivos de saída podem ser descompactados corretamente.

O script que você mostrou tinha lepton no final. Você pode fazer isso se quiser. Isso faz com que o script sempre relate o sucesso. Caso contrário, o script retornará o status de saída do último comando executado - o que é provavelmente preferível. De qualquer forma, ele pode relatar sucesso mesmo se exit 0 , find ou file encontrar problemas, se o comando último lepton tiver sido bem-sucedido. Você pode, é claro, expandir o script com um código de tratamento de erros mais sofisticado.

8. Talvez você queira os caminhos, também

Se você deseja gerar uma lista de caminhos separados da própria saída de lepton , aproveite o comportamento de gravação de lepton para erro padrão imprimindo os caminhos para saída padrão . Nesse caso, você provavelmente deseja imprimir apenas os caminhos e não uma mensagem "Processando". Opcionalmente, você pode querer terminar os caminhos com caracteres nulos em vez de novas linhas, assim você pode processar a lista sem quebrar caminhos que contenham novas linhas.

#!/bin/bash

case "" in
    -0) format='%s
xargs -0 printf '[%s]\n\n' < out
';; *) format='%s\n';; esac find . -exec file --mime-type -r0F '' {} + | while read -rd ''; do read -r mimetype case "$mimetype" in image/jpeg) printf "$format" "$REPLY" lepton "$REPLY" esac done

Quando você executa esse script, pode passar o sinal lepton para emitir caracteres nulos em vez de novas linhas. Esse script não faz o processamento de opção no estilo Unix: ele apenas verifica o argumento primeiro que você passa; passar a bandeira repetidamente no mesmo argumento ( -0 ) não funciona; e nenhuma mensagem de erro relacionada à opção é gerada. Essa limitação é por brevidade, e porque você provavelmente não precisa de nada mais sofisticado, pois o script não suporta argumentos que não sejam de opção e -00 é a única opção possível.

No meu sistema, chamei esse script de -0 e coloquei em jpeg-lep3 , depois executei ~/source , que imprimiu apenas a saída de ~/source/jpeg-lep3 -0 > out para o meu terminal. Se você fizer algo assim, poderá testar se os caracteres nulos foram escritos corretamente entre os caminhos usando:

%pre%     
por Eliah Kagan 30.08.2017 / 19:49
1

Você tem find e verifique com o comando file para seu tipo mime também.

find . -type f -exec file --mime-type -b '{}' +

Ou para completar como segue:

find . -type f -exec sh -c '
    file --mime-type -b "
find -type f -print0 | xargs -0 identify
" | grep -q "aPATTERN" && printf "%pre%\n" ' {} \;

Ou a identify opção dos pacotes do ImageMagic .

%pre%     
por αғsнιη 26.08.2017 / 22:18