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$filevariável fora doforloop aqui:basename -s .txt "$file"... ou eu entendi mal?