Sei que posso resolver esse problema de várias maneiras, mas estou me perguntando se existe uma maneira de fazê-lo usando apenas os bash built-ins e, se não, qual é a maneira mais eficiente de fazer isso.
Eu tenho um arquivo com conteúdos como
AAA
B C DDD
FOO BAR
pelo que quero dizer apenas que ele tem várias linhas e cada linha pode ou não ter espaços. Eu quero executar um comando como
cmd AAA "B C DDD" "FOO BAR"
Se eu usar cmd $(< file)
eu recebo
cmd AAA B C DDD FOO BAR
e se eu usar cmd "$(< file)"
eu recebo
cmd "AAA B C DDD FOO BAR"
Como obtenho cada linha tratada com exatamente um parâmetro?
bash
command-substitution
Old Pro
fonte
fonte
Respostas:
Portably:
Ou usando um subshell para fazer as
IFS
alterações na opção e local:O shell executa divisão de campo e geração de nome de arquivo no resultado de uma substituição de variável ou comando que não está entre aspas duplas. Portanto, você precisa desativar a geração de nome de arquivo com
set -f
e configurar a divisão de campo comIFS
para fazer apenas novas linhas separarem campos.Não há muito a ganhar com construções bash ou ksh. Você pode tornar
IFS
local uma função, mas nãoset -f
.No bash ou no ksh93, você pode armazenar os campos em uma matriz, se precisar passá-los para vários comandos. Você precisa controlar a expansão no momento em que cria a matriz. Em seguida,
"${a[@]}"
expande para os elementos da matriz, um por palavra.fonte
Você pode fazer isso com uma matriz temporária.
Configuração:
Preencha a matriz:
Use a matriz:
Não é possível descobrir uma maneira de fazer isso sem essa variável temporária - a menos que a
IFS
alteração não seja importantecmd
, nesse caso:deve fazê-lo.
fonte
IFS
sempre me deixa confusa.IFS=$'\n' cmd $(<input)
não funcionaIFS=$'\n'; cmd $(<input); unset IFS
funciona. Por quê? Eu acho que vou usar(IFS=$'\n'; cmd $(<input))
IFS=$'\n' cmd $(<input)
não funciona porque apenas defineIFS
no ambiente decmd
.$(<input)
é expandido para formar o comando, antes que a atribuiçãoIFS
seja executada.Parece que a maneira canônica de fazer isso
bash
é algo comoou, se sua versão do bash tiver
mapfile
:A única diferença que posso encontrar entre o mapfile e o loop while-read versus o one-liner
é que o primeiro converterá uma linha em branco em um argumento vazio, enquanto o one-liner ignorará uma linha em branco. Nesse caso, o comportamento de uma linha é o que eu preferiria de qualquer maneira, portanto, um bônus duplo por ser compacto.
Eu usaria,
IFS=$'\n' cmd $(<file)
mas não funciona, porque$(<file)
é interpretado para formar a linha de comando antes deIFS=$'\n'
entrar em vigor.Embora não funcione no meu caso, agora aprendi que muitas ferramentas suportam linhas de terminação, em
null (\000)
vez dasnewline (\n)
quais facilitam muito isso quando se lida com, por exemplo, nomes de arquivos, fontes comuns dessas situações :alimenta uma lista de nomes de arquivos totalmente qualificados como argumentos para o md5 sem globbing, interpolação ou qualquer outra coisa. Isso leva à solução não integrada
embora isso também ignore linhas vazias, mas captura linhas que possuem apenas espaço em branco.
fonte
cmd $(<file)
valores sem citar (usando a capacidade do bash de dividir palavras) é sempre uma aposta arriscada. Se houver alguma linha,*
ela será expandida pelo shell para uma lista de arquivos.Você pode usar o bash embutido
mapfile
para ler o arquivo em uma matrizou, não testado,
xargs
pode fazê-lofonte
xargs
também não ajuda.xargs -d
ouxargs -L
-d
opção exargs -L 1
executa o comando uma vez por linha, mas ainda divide os argumentos no espaço em branco.mapfile
é muito útil para mim, pois pega linhas em branco como itens de matriz, o que oIFS
método não faz.IFS
trata as novas linhas contíguas como um único delimitador ... Obrigado por apresentá-lo, como eu não estava ciente do comando (embora, com base nos dados de entrada do OP e na linha de comando esperada, parece que ele realmente deseja ignorar as linhas em branco).A melhor maneira que eu pude encontrar. Apenas funciona.
fonte
IFS
espaço, mas a questão é não dividir no espaço?Arquivo
O loop mais básico (portátil) para dividir um arquivo em novas linhas é:
Qual será impresso:
Sim, com IFS padrão = spacetabnewline.
Por que funciona
IFS
necessária.set -f
necessário.-r
opção (bruta) é evitar a remoção da maioria das barras invertidas.Isso não funcionará se for necessária a divisão e / ou globbing. Nesses casos, é necessária uma estrutura mais complexa.
Se você precisar (ainda portátil) de:
IFS= read -r line
IFS=':' read -r a b c
.Divida o arquivo em algum outro caractere (não portátil, funciona com ksh, bash, zsh):
Expansão
Obviamente, o título da sua pergunta é sobre a divisão de uma execução de comando em novas linhas, evitando a divisão em espaços.
A única maneira de se separar do shell é deixar uma expansão sem aspas:
Isso é controlado pelo valor do IFS e, em expansões não citadas, o globbing também é aplicado. Para mkae esse trabalho, você precisa:
Desative a opção de concha de cobertura
set +f
:set + f IFS = '' cmd $ (<arquivo)
Obviamente, isso altera o valor do IFS e de globbing pelo resto do script.
fonte