Estas soluções que você liga são de fato muito boas. Algumas respostas podem não ter explicação, então vamos resolver, adicionar mais algumas talvez.
Esta sua linha
for file in *.txt
indica que a extensão é conhecida de antemão (nota: os ambientes compatíveis com POSIX diferenciam maiúsculas e minúsculas, *.txt
não corresponderá FOO.TXT
). Nesse caso
basename -s .txt "$file"
deve retornar o nome sem a extensão ( basename
também remove o caminho do diretório: /directory/path/filename
& amp; rightarrow; filename
; no seu caso, não importa porque $file
não contém esse caminho). Para usar a ferramenta em seu código, você precisa de uma substituição de comando que se pareça com isso em geral: $(some_command)
. A substituição de comandos leva a saída de some_command
, trata-o como uma string e coloca-o onde $(…)
é. Seu redirecionamento particular será
… > "./$(basename -s .txt "$file")_sorted.txt"
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^ the output of basename will replace this
Citações aninhadas são OK aqui porque Bash é inteligente o suficiente para saber as citações dentro $(…)
estão emparelhados juntos.
Isso pode ser melhorado. Nota basename
é um executável separado, não um shell embutido (no Bash run type basename
, comparado a type cd
). Gerar qualquer processo extra é custoso, requer recursos e tempo. Desovar em um loop geralmente tem um desempenho ruim. Portanto, você deve usar o que a shell lhe oferece para evitar processos extras. Neste caso, a solução é:
… > "./${file%.txt}_sorted.txt"
A sintaxe é explicada abaixo para um caso mais geral.
Caso você não saiba a extensão:
… > "./${file%.*}_sorted.${file##*.}"
A sintaxe explicada:
${file#*.}
- $file
, mas a string mais curta correspondente *.
é removido da frente;
${file##*.}
- $file
, mas a string mais longa correspondente *.
é removido da frente; use-o para obter apenas uma extensão;
${file%.*}
- $file
, mas a string mais curta correspondente .*
é removido do final; use-o para obter tudo menos a extensão;
${file%%.*}
- $file
, mas com a correspondência de cadeia mais longa .*
é removido do final;
Correspondência de padrões é glob-like, não regex. Isso significa *
é um curinga para zero ou mais caracteres, ?
é um curinga para exatamente um caractere (não precisamos ?
no seu caso embora). Quando você invoca ls *.txt
ou for file in *.txt;
você está usando o mesmo mecanismo de correspondência de padrões. Um padrão sem curingas é permitido. Nós já usamos ${file%.txt}
Onde .txt
é o padrão.
Exemplo:
$ file=name.name2.name3.ext
$ echo "${file#*.}"
name2.name3.ext
$ echo "${file##*.}"
ext
$ echo "${file%.*}"
name.name2.name3
$ echo "${file%%.*}"
name
Mas cuidado:
$ file=extensionless
$ echo "${file#*.}"
extensionless
$ echo "${file##*.}"
extensionless
$ echo "${file%.*}"
extensionless
$ echo "${file%%.*}"
extensionless
Por esta razão, a seguinte engenhoca poderia ser útil (mas não é, explicação abaixo):
${file#${file%.*}}
Ele funciona identificando tudo menos a extensão ( ${file%.*}
), em seguida, remove isso da cadeia inteira. Os resultados são assim:
$ file=name.name2.name3.ext
$ echo "${file#${file%.*}}"
.ext
$ file=extensionless
$ echo "${file#${file%.*}}"
$ # empty output above
Note o .
está incluído neste momento. Você pode obter resultados inesperados se $file
literalmente contido *
ou ?
; mas o Windows (onde as extensões são importantes) não permite esses caracteres em nomes de arquivos de qualquer maneira, então você pode não se importar. Contudo […]
ou {…}
, se presente, pode desencadear seu próprio esquema de correspondência de padrões e quebrar a solução!
Seu redirecionamento "melhorado" seria:
… > "./${file%.*}_sorted${file#${file%.*}}"
Deve suportar nomes de arquivos com ou sem extensão, embora não com colchetes ou chaves, infelizmente. Uma pena. Para corrigi-lo, você precisa duplicar a variável interna.
Redirecionamento realmente melhorado:
… > "./${file%.*}_sorted${file#"${file%.*}"}"
Cotação dupla faz ${file%.*}
não atuar como um padrão! Bash é inteligente o suficiente para distinguir citações internas e externas porque as internas estão embutidas no exterior ${…}
sintaxe. Eu acho que esse é o jeito certo .
Outra solução (imperfeita), vamos analisá-lo por razões educacionais:
${file/./_sorted.}
Substitui o primeiro .
com _sorted.
. Ele funcionará bem se você tiver no máximo um ponto $file
. Existe uma sintaxe semelhante ${file//./_sorted.}
que substitui todos os pontos. Tanto quanto eu sei, não há variante para substituir o último ponto apenas.
Ainda a solução inicial para arquivos com .
parece robusto. A solução para sem extensão $file
é trivial: ${file}_sorted
. Agora tudo o que precisamos é uma maneira de distinguir os dois casos. Aqui está:
[[ "$file" == *?.* ]]
Retorna o status de saída 0 (verdadeiro) se e somente se o conteúdo de $file
variável corresponde ao padrão do lado direito. O padrão diz "há um ponto depois de pelo menos um caractere" ou "há um ponto que não está no começo". O objetivo é tratar os arquivos ocultos do Linux (por exemplo, .bashrc
) como sem extensão, a menos que haja outro pontilhe em algum lugar.
Note que precisamos [[
aqui não [
. O primeiro é mais poderoso, mas infelizmente não portável ; o último é portátil, mas muito limitado para nós.
A lógica agora é assim:
[[ "$file" == *?.* ]] && file1="./${file%.*}_sorted.${file##*.}" || file1="${file}_sorted"
Depois disto, $file1
contém o nome desejado, portanto seu redirecionamento deve ser
… > "./$file1"
E todo o trecho de código ( *.txt
substituído por *
para indicar que trabalhamos com qualquer extensão ou sem extensão):
for file in *;
do
printf 'Processing %s\n' "$file"
[[ "$file" == *?.* ]] && file1="./${file%.*}_sorted.${file##*.}" || file1="${file}_sorted"
LC_ALL=C sort -u "$file" > "./$file1"
done
Isso tentaria processar diretórios (se houver) também; você já sabe o que fazer para fixar isso.
… > "./${file%.txt}_sorted.txt"
"evita processos extras" - é porque estamos usando basename no$file
variável fora dofor
loop aqui:basename -s .txt "$file"
... ou eu entendi mal?