
Em um script bash, tenho a seguinte variável:
file_name='this_is_the_hart_part.csv'
Usando
var2=$(echo $file_name | sed -e 's/_{2}\(.*\)_{3}/\1/')
Quero extrair a substring "the" (entre os sublinhados número 2 e 3 na variável $ file_name).
Mas recebo de volta $var2 igual a $file_name. Como devo alterar meu comando sed?
Responder1
Os tipos de expressões regulares suportados por sed
não permitem correspondência não gananciosa com *
.
Você deseja obter o terceiro _
campo delimitado. Isso é mais fácil de fazer com cut
:
cut -d '_' -f 3
Ou com awk
:
awk -F '_' '{ print $3 }'
Ou, no shell, removendo os dois primeiros campos em sucessão e aparando o final:
str=${file_name#*_}
str=${str#*_}
str=${str%%_*}
"$str"
seria a palavra the
no final. Usar esta última variação provavelmente seria a maneira mais rápida e robusta de sair dessas três.
A substituição da variável ${variable#*_}
resultaria em uma string $variable
com o bit inicial até e incluindo o primeiro sublinhado removido. Isso ${variable%%_*}
removeria tudo, desde o primeiro sublinhado até o final de $variable
. Estas são substituições de variáveis padrão.
O benefício de usar a substituição de variável em um nome de arquivo é que ela lidaria com nomes de arquivos contendo novas linhas, o que nem awk
nem sed
faria cut
. Em geral, não use ferramentas de edição de texto orientadas a linhas em nomes de arquivos.
Além disso, você está usando echo $file_name
. Como $file_name
não está entre aspas, ele sofreria divisão de palavras (em cada caractere que também faz parte de $IFS
; um espaço, tabulação e nova linha por padrão) e as palavras geradas, se contivessem caracteres globbing de nome de arquivo, seriam comparadas com nomes de arquivos no diretório atual pela casca. E as barras invertidas no nome do arquivo também podem desaparecer ou ter efeitos indesejados (mesmo se você citar a expansão). O ksh
shell também faria expansões no valor de $file_name
quando não está entre aspas.
Responder2
Primeira observação que sed
é umtextoutilitário que funciona por padrão em uma linha por vez, enquanto os nomes de arquivos podem conter qualquer caractere (incluindo nova linha) e até mesmo não-caracteres (podem ser não-caracteres).texto).
Também,deixar uma variável sem aspas tem um significado muito especial, você quase nunca quer fazer isso, também épotencialmente muito perigoso.
Também,você não pode usar echo
para gerar dados arbitrários, use printf
em vez disso.
Além disso, a sintaxe de atribuição de variável em shells do tipo Bourne é: var=value
, não $var=value
.
Você pode carregar toda a saída de echo
(ou melhor, printf
) no sed
espaço padrão de com:
printf '%s\n' "$filename" | sed -e :1 -e '$!{N;b1' -e '}'
Então, você pode adicionar o código para extrair a parte entre o segundo e o terceiro _
:
var2=$(
printf '%s\n' "$filename" |
sed -ne :1 -e '$!{N;b1' -e '}' -e 's/^\([^_]*_\)\{2\}\([^_]*\)_.*/\2/p'
)
A parte não gananciosa é abordada usando [^_]*
(uma sequência de não- _
caracteres) que, ao contrário das .*
garantias, não correspondemos _
aos limites anteriores (embora ainda engasgasse com não-caracteres em muitas implementações).
Neste caso aqui, você poderia usar operadores de expansão de parâmetros do shell:
case $filename in
(*_*_*_*) var2=${filename#*_*_}; var2=${var2%%_*};;
(*) var2=;;
esac
O que funcionaria melhor se o nome do arquivo não fosse texto ou se a parte que você deseja extrair terminasse em um caractere de nova linha (e também seria mais eficiente).
Alguns shells gostam zsh
ou ksh93
possuem operadores mais avançados:
zsh
:divida
_
e obtenha o terceiro campo:var2=${"${(@s:_:)filename}"[3]}
Usando as
${var/pattern/replacement}
referências anteriores e (nesse caso, você deseja verificar primeiro se a variável contém pelo menos 3 sublinhados ou não haverá nenhuma substituição).set -o extendedglob var2=${filename/(#b)*_*_(*)_*/$match[1]}
ksh93
:var2=${filename/*_*_@(*)_*/\1}
Responder3
@Kusalananda está certo, sed
é a ferramenta errada e você não pode fazer uma correspondência não gananciosa. Mas você pode usar uma solução alternativa para correspondência não gananciosa:
[^_]*
corresponderá a qualquer caractere que não seja_
Então, no seu caso, você poderia fazer algo assim:
printf '%s\n' "$file_name" | sed -e 's/^[^_]*_[^_]*_\([^_]*\).*$/\1/g'
Mas... para o seu caso de uso, é melhor usar outras ferramentas...