Por que o sed não está reconhecendo \ t como uma guia?

105
sed "s/\(.*\)/\t\1/" $filename > $sedTmpFile && mv $sedTmpFile $filename

Estou esperando que este sedscript insira um tabna frente de cada linha, $filenamemas não é. Por algum motivo, ele está inserindo um t.

sessenta pés cara
fonte
1
Como o sed pode variar entre as plataformas (em particular, BSD / MacOSX versus Linux), pode ser útil especificar a plataforma na qual você está usando o sed.
Isaac
sed "s / (. *) / # \ 1 /" $ nome do arquivo | tr '#' '\ t'> $ sedTmpFile && mv $ sedTmpFile $ filename.
user2432405
Para usuários do OS X (macOS), consulte esta pergunta .
Franklin Yu

Respostas:

129

Nem todas as versões de sedentendem \t. Basta inserir uma guia literal (pressione Ctrl- e Vdepois Tab).

Mark Byers
fonte
2
Ah sim; para esclarecer: nem todas as versões do sed entendem \tna parte de substituição da expressão (ele reconheceu \tna parte de correspondência de padrão muito bem)
John Weldon
3
awwwwwwwwwwwwwwwwwwwww, ok, isso é muito interessante. E estranho. Por que você o faria reconhecê-lo em um lugar, mas não no outro ...?
sixtyfootersdude,
2
Chamado de um script, isso não funcionará: as guias seriam ignoradas por sh. Por exemplo, o seguinte código de um script de shell adicionará $ TEXT_TO_ADD, sem precedê-lo por uma tabulação: sed "$ {LINE} a \\ $ TEXT_TO_ADD" $ FILE.
Dereckson
2
@Dereckson e outros - veja esta resposta: stackoverflow.com/a/2623007/48082
Cheeso
2
Dereckson s / pode / não pode /?
Douglas Realizado em
41

Usando o Bash, você pode inserir um caractere TAB programaticamente, assim:

TAB=$'\t' 
echo 'line' | sed "s/.*/${TAB}&/g" 
echo 'line' | sed 's/.*/'"${TAB}"'&/g'   # use of Bash string concatenation
seduzir
fonte
Isso é muito útil.
Cheeso
1
Você estava no caminho certo com a $'string'explicação, mas falta. Na verdade, eu suspeito que, devido ao uso extremamente estranho, você provavelmente tem um entendimento incompleto (como a maioria de nós faz com o bash). Veja minha explicação abaixo: stackoverflow.com/a/43190120/117471
Bruno Bronosky
1
Lembre-se de que o BASH não expande variáveis ​​como $TABaspas simples, portanto, você precisará usá-lo com aspas duplas.
nealmcb
Cuidado ao usar *aspas duplas ... isso será tratado como um glob, não como a regex que você pretende.
levigroker
27

@sedit estava no caminho certo, mas é um pouco estranho definir uma variável.

Solução (específico do bash)

A maneira de fazer isso no bash é colocar um cifrão antes da string entre aspas simples.

$ echo -e '1\n2\n3'
1
2
3

$ echo -e '1\n2\n3' | sed 's/.*/\t&/g'
t1
t2
t3

$ echo -e '1\n2\n3' | sed $'s/.*/\t&/g'
    1
    2
    3

Se sua string precisa incluir expansão de variável, você pode colocar strings entre aspas assim:

$ timestamp=$(date +%s)
$ echo -e '1\n2\n3' | sed "s/.*/$timestamp"$'\t&/g'
1491237958  1
1491237958  2
1491237958  3

Explicação

Em bash $'string'causa "expansão ANSI-C". E é isso que a maioria de nós esperar quando usamos coisas como \t, \r, \n, etc. De: https://www.gnu.org/software/bash/manual/html_node/ANSI_002dC-Quoting.html#ANSI_002dC-Quoting

Palavras da forma $ 'string' são tratadas de maneira especial. A palavra se expande para string , com caracteres de escape de barra invertida substituídos conforme especificado pelo padrão ANSI C. Seqüências de escape de barra invertida, se presentes, são decodificadas ...

O resultado expandido é entre aspas simples, como se o cifrão não estivesse presente.

Solução (se você deve evitar bash)

Eu pessoalmente acho que a maioria dos esforços para evitar o bash são tolos porque evitar o bash NÃO * torna seu código portátil. (Seu código será menos frágil se você explicá-lo do bash -euque se você tentar evitar bash e usar sh[a menos que você seja um ninja POSIX absoluto].) Mas, em vez de ter um argumento religioso sobre isso, vou apenas dar-lhe o MELHOR * responda.

$ echo -e '1\n2\n3' | sed "s/.*/$(printf '\t')&/g"
    1
    2
    3

* Melhor resposta? Sim, porque um exemplo do que a maioria dos scripts de shell anti-bash faria de errado em seu código é usar echo '\t'como na resposta de @robrecord . Isso funcionará com o GNU echo, mas não com o BSD echo. Isso é explicado pelo The Open Group em http://pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html#tag_20_37_16 E este é um exemplo de por que tentar evitar bashismos geralmente falham.

Bruno Bronosky
fonte
8

Usei algo assim com um shell Bash no Ubuntu 12.04 (LTS):

Para anexar uma nova linha com tab, segundo quando a primeira é correspondida:

sed -i '/first/a \\t second' filename

Para substituir a primeira pela guia, a segunda :

sed -i 's/first/\\t second/g' filename
Thomas Bratt
fonte
4
O duplo escape é a chave, ou seja, use \\te não \t.
zamnuts
Eu também tive que usar aspas duplas em vez de aspas simples no Ubuntu 16.04 e no Bash 4.3.
caw
4

Use $(echo '\t'). Você precisará de aspas em torno do padrão.

Por exemplo. Para remover uma guia:

sed "s/$(echo '\t')//"
Robrecord
fonte
5
É engraçado que você esteja usando um recurso específico do "GNU echo" (interpretando \ t como um caractere de tabulação) para resolver um bug específico do "sed BSD" (interpretando \ t como 2 caracteres separados). Presumivelmente, se você tiver "GNU echo", também terá "GNU sed". Nesse caso, você não precisaria usar o eco. Com BSD echo echo '\t'vai produzir 2 caracteres separados. A maneira portátil POSIX é usar printf '\t'. É por isso que digo: não tente tornar seu código portátil não usando o bash. É mais difícil do que você pensa. Usar bashé a coisa mais portátil que a maioria de nós pode fazer.
Bruno Bronosky,
3

Você não precisa usar sedpara fazer uma substituição quando, na verdade, deseja apenas inserir uma guia na frente da linha. A substituição desta caixa é uma operação cara em comparação com apenas imprimi-la, especialmente quando você está trabalhando com arquivos grandes. É mais fácil de ler também, pois não é regex.

por exemplo, usando awk

awk '{print "\t"$0}' $filename > temp && mv temp $filename
ghostdog74
fonte
0

sednão suporta \t, nem outras sequências de escape como \npara esse assunto. A única maneira que descobri de fazer isso foi realmente inserir o caractere de tabulação no script usando sed.

Dito isso, você pode querer considerar o uso de Perl ou Python. Aqui está um pequeno script Python que escrevi e uso para todas as expressões regulares de fluxo:

#!/usr/bin/env python
import sys
import re

def main(args):
  if len(args) < 2:
    print >> sys.stderr, 'Usage: <search-pattern> <replace-expr>'
    raise SystemExit

  p = re.compile(args[0], re.MULTILINE | re.DOTALL)
  s = sys.stdin.read()
  print p.sub(args[1], s),

if __name__ == '__main__':
  main(sys.argv[1:])
Roman Nurik
fonte
2
E a versão Perl seria o shell de uma linha "perl -pe 's / a / b /' nome do arquivo" ou "something | perl -pe 's / a / b /'"
tiftik de
0

Em vez de BSD sed, eu uso perl:

ct@MBA45:~$ python -c "print('\t\t\thi')" |perl -0777pe "s/\t/ /g"
   hi
Cees Timmerman
fonte
0

Eu acho que os outros ter esclarecido este adequadamente para outras abordagens ( sed, AWK, etc.). No entanto, bashseguem minhas respostas específicas (testadas no macOS High Sierra e CentOS 6/7).

1) Se o OP quiser usar um método de pesquisa e substituição semelhante ao que propôs originalmente, eu sugeriria o uso perlpara isso, como segue. Notas: barras invertidas antes dos parênteses para regex não devem ser necessárias, e esta linha de código reflete como $1é melhor usar do que \1com o perloperador de substituição (por exemplo, por documentação do Perl 5 ).

perl -pe 's/(.*)/\t$1/' $filename > $sedTmpFile && mv $sedTmpFile $filename

2) No entanto, como apontado por ghostdog74 , uma vez que a operação desejada é simplesmente adicionar uma guia no início de cada linha antes de alterar o arquivo tmp para o arquivo de entrada / destino ( $filename), recomendo perlnovamente, mas com a seguinte modificação (s):

perl -pe 's/^/\t/' $filename > $sedTmpFile && mv $sedTmpFile $filename
## OR
perl -pe $'s/^/\t/' $filename > $sedTmpFile && mv $sedTmpFile $filename

3) Claro, o arquivo tmp é supérfluo , então é melhor apenas fazer tudo 'no lugar' (adicionando -isinalizador) e simplificar as coisas para uma linha mais elegante com

perl -i -pe $'s/^/\t/' $filename
Justincbagley
fonte