Escapando aspas duplas para variáveis em bash e qmake

3

Eu escrevi o seguinte arquivo meta-projeto para o qmake que é projetado para criar todos os arquivos .pro nos subdiretórios a seguir (por razões históricas e por causa de outros toolchains, os nomes dos arquivos não correspondem aos nomes das pastas). O que essencialmente se resume a algo como isto (jogando de lado coisas específicas do projeto)

THIS_FILE=make-all.pro
TEMPLATE=subdirs
FIND= "find -name \'*.pro\' -printf \'%p\n\'"
AWK= "awk \'{$1=substr($1,3); print}\'"
RMTHIS= "awk \'!/$$THIS_FILE/\'"
SUBDIRS= $$system($$FIND | $$AWK | $$RMTHIS )

Eu preciso de uma lista de pastas que contenham .pro arquivos dentro de um script Bash, então eu decidi copiar o método

#!/bin/bash
FIND="find -name '*.pro' -printf '%h\n"
AWK="awk '{\=substr(\,3);printf}'"
SUBDIRS=$($FIND | $AWK)

Aparentemente, isso não funciona, awk estava vomitando erro invalid char '' na expressão. Tentar executar as mesmas linhas no Bash diretamente mostrou que awk funciona apenas se forem usadas aspas duplas

find -name '*.pro' | awk "{\=substr(\,3);printf}"

Substituindo a linha em questão por

AWK='awk "{$1=substr($1,3);printf}" '

não deu resultado de trabalho, a saída do script está vazia, ao contrário da saída do comando inserido manualmente. Aparentemente

 find -name '*.pro' 

No script encontra apenas arquivos na pasta atual, enquanto sua contraparte na linha de comando do bash o encontra em subpastas. O que está errado e porque o qmake funciona de maneira diferente?

    
por Swift 03.05.2017 / 15:27

2 respostas

2

Eu não estou familiarizado com a sintaxe do qmake, mas a partir de sua amostra usa citações e variáveis de maneiras muito diferentes das shells. Então você não pode simplesmente usar o mesmo código.

link abrange o que você precisa saber, então aqui vou resumir o que é relevante para sua pergunta.

Em suma, você não pode simplesmente colocar um comando shell em uma string. Shells como bash não analisam strings recursivamente. Eles analisam o código-fonte e criam comandos como lista de strings: um comando simples consiste em um nome de comando (caminho para um executável, nome da função, etc.) e seus argumentos.

Quando você usa uma atribuição como AWK="awk '{\=substr(\,3);printf}'" , isso define a variável AWK como uma string; quando você usa a variável como $AWK fora de aspas duplas, isso transforma o valor da variável em uma lista de strings, analisando-a no espaço em branco, mas não a analisa como código shell, então caracteres como ' acabam sendo literalmente no argumento. Isso raramente é desejável, e é por isso que o conselho geral sobre o uso de variáveis no shell é colocar aspas duplas em torno de expansões de variáveis a menos que você saiba que precisa deixá-las fora e entender o que isso implica. (Note que a minha resposta aqui não conta toda a história).

No bash, você pode colocar um comando simples em uma matriz.

#!/bin/bash
FIND=(find -name '*.pro' -printf '%h\n')
AWK=(awk '{$1=substr($1,3);print}')
SUBDIRS=$("${FIND[@]}" | "${AWK[@]}") 

Mas geralmente a melhor maneira de armazenar um comando é definir uma função. Isto não se limita a um simples comando: desta forma você pode ter redirecionamentos, atribuições de variáveis, condicionais, etc.

#!/bin/bash
find_pro_files () {
  find -name '*.pro' -printf '%h\n'
}
truncate_names () {
  awk '{$1=substr($1,3);print}'
}
SUBDIRS=$(find_pro_files | truncate_names)

Não sei ao certo o que você está tentando fazer com esse script (especialmente considerando que você altera o código find e awk entre os snippets de código), mas provavelmente há uma maneira melhor de fazer isso. Você pode fazer loop sobre *.pro arquivos em subdiretórios do diretório atual com

for pro in */*.pro; do
  subdir="${pro%/*}"
  basename="${pro##*/}"
  …
done

Se você quiser percorrer subdiretórios recursivamente, no bash, você pode usar **/*.pro em vez de */*.pro , mas tenha cuidado com isso também recorre a links simbólicos para diretórios.

    
por 04.05.2017 / 01:56
1

Se tudo o que você precisa fazer é encontrar diretórios que contenham diretamente um arquivo com extensão .pro , você pode usar xargs e, opcionalmente, uniq, se quiser listar um diretório apenas uma vez, mesmo que contenha vários arquivos pro :

find -type f -name "*.pro" | xargs -I{} dirname {} | uniq

Se você precisar de caminhos absolutos em vez de relativos:

find -type f -name "*.pro" | xargs -I{} dirname {} | xargs readlink -f | uniq

Como você solicitou, uma solução usando o awk:

find -type f -name "*.pro" | awk -F/ 'BEGIN { OFS="/" } { $1=""; $NF=""; print $0 }' | cut -c 2- | uniq

O que lhe dá exatamente o que você precisa; caminhos relativos sem o prefixo ./ .

    
por 03.05.2017 / 16:24