Reconheço que há perguntas superficialmente semelhantes feitas aqui antes, mas todas as que vi são mais simples do que o que estou tentando alcançar. Soluções somente Bash são preferidas.
Tenho uma variável contendo uma string que parece uma comparação de algum tipo, e gostaria de dividi-la em um array. A seguir estão alguns exemplos, incluindo como gostaria que fossem divididos:
var='name="value"' # arr=([0]=name [1]='=' [2]=value)
var="name != '!value='" # arr=([0]=name [1]='!=' [2]='!value=')
var='"na=me" = value' # arr=([0]=na=me [1]='=' [2]=value)
var='name >= value' # arr=([0]=name [1]='>=' [2]=value)
var='name' # arr=([0]=name)
var='name = "escaped \"quotes\""' # arr=([0]=name [1]='=' [2]=escaped\ \"quotes\")
var="name = \"nested 'quotes'\"" # arr=([0]=name [1]='=' [2]=nested\ \'quotes\')
var="name = 'nested \"quotes\"'" # arr=([0]=name [1]='=' [2]=nested\ \"quotes\")
Você entendeu. Qualquer um dos lados (ou nenhum) pode ser citado, com aspas simples ou duplas. Pode haver aspas de escape ou aninhadas. O operador entre elas pode ser qualquer um de um conjunto predefinido, mas também pode ser incluído dentro das strings citadas. Pode haver ou não espaços. Pode não haver operador algum.
Tenho que analisar muitas linhas e, portanto, prefiro não bifurcar um novo processo a cada vez, e é por isso que soluções somente Bash são preferidas. Esta é uma adição a um script Bash existente que não precisa ser portátil para outros shells e está sendo executado no Bash 5.2, então tenho acesso a recursos Bash modernos que podem ser úteis.
IFS=\" read -a arr <<<"$var"
é legal porque entende como lidar com aspas de escape, e se eu tivesse que lidar apenas com aspas simples ou duplas e não ambas , eu poderia fazer isso funcionar. Do jeito que está, só espero não ter que escrever um algoritmo tokenizador inteiro em script de shell, e que haja alguma combinação de recursos que eu não tenha considerado que possa analisar isso de forma confiável.
Você precisa escrever um parser: leia a string caractere por caractere, com base no caractere atual, estenda a palavra atual ou comece uma nova. Mantenha um sinalizador para indicar que o parser está dentro de uma string entre aspas.
Algo como isto:
Ele analisa corretamente todos os exemplos que você deu, mas está longe de terminar (não verifica aspas não fechadas etc.)
Como @choroba apontou, você provavelmente não pode evitar escrever um lexer para dividir suas strings de entrada. Felizmente, "escanear" token por token com um ERE é o suficiente. Eu diria que usar uma linguagem com grupos "não-capturantes" e "nomeados" seria a melhor escolha, mas se você estiver preso ao Bash, aqui está como você pode fazer isso:
nota: requer bash 4.3+
saída:
Para o leitor corrigir:
Fiz algumas suposições sobre o que são um "varname" e um "operador". Basicamente, um "varname" é composto de caracteres alfanuméricos/sublinhados; e um "operador" é qualquer coisa que não contenha espaços (excluindo uma palavra e uma string entre aspas).
Embora a regex consuma qualquer sequência de escape de barra invertida presente em uma string entre aspas duplas, somente
\"
ela é interpretada; talvez seja necessário implementar a decodificação de outras sequências de escape também.