¿Por qué sed no reconoce \ t como una pestaña?

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

Espero que este sedscript inserte un tabdelante de cada línea, $filenamepero no es así. Por alguna razón, está insertando un t.

Sixtyfootersdude
fuente
1
Como sed puede variar entre plataformas (en particular, BSD / MacOSX versus Linux), puede ser útil especificar la plataforma en la que está utilizando sed.
Isaac
sed "s / (. *) / # \ 1 /" $ nombre de archivo | tr '#' '\ t'> $ sedTmpFile && mv $ sedTmpFile $ nombre de archivo.
user2432405
Para usuarios de OS X (macOS), consulte esta pregunta .
Franklin Yu

Respuestas:

129

No todas las versiones de sedentender \t. Simplemente inserte una pestaña literal en su lugar (presione Ctrl- Vluego Tab).

Mark Byers
fuente
2
Ah, sí; para aclarar: no todas las versiones de sed entienden \ten la parte de reemplazo de la expresión (reconoció \ten la parte de coincidencia de patrones muy bien)
John Weldon
3
awwwwwwwwwwwwwwwwwww, está bien, eso es bastante interesante. Y extraño. ¿Por qué harías que lo reconociera en un lugar pero no en el otro ...?
sixtyfootersdude
2
Llamado desde un script, eso no funcionará: sh ignoraría las pestañas. Por ejemplo, el siguiente código de un script de shell agregará $ TEXT_TO_ADD, sin anteponerlo con una tabulación: sed "$ {LINE} a \\ $ TEXT_TO_ADD" $ FILE.
Dereckson
2
@Dereckson y otros: vea esta respuesta: stackoverflow.com/a/2623007/48082
Cheeso
2
Dereckson s / can / can't /?
Douglas se celebró el
41

Usando Bash, puede insertar un carácter TAB programáticamente así:

TAB=$'\t' 
echo 'line' | sed "s/.*/${TAB}&/g" 
echo 'line' | sed 's/.*/'"${TAB}"'&/g'   # use of Bash string concatenation
sedit
fuente
Esto es muy útil.
Cheeso
1
Estabas en el camino correcto con la $'string'pero falta explicación. De hecho, sospecho que debido al uso extremadamente incómodo, probablemente tenga una comprensión incompleta (como la mayoría de nosotros lo hacemos con bash). Vea mi explicación a continuación: stackoverflow.com/a/43190120/117471
Bruno Bronosky
1
Recuerde que BASH no expandirá variables como $TABdentro de comillas simples, por lo que deberá usarlo entre comillas dobles.
nealmcb
Tenga cuidado con el uso de *comillas dobles internas ... esto se tratará como un glob, no como la expresión regular que desea.
levigroker
27

@sedit estaba en el camino correcto, pero es un poco incómodo definir una variable.

Solución (específica de bash)

La forma de hacer esto en bash es poner un signo de dólar delante de su cadena entre comillas.

$ 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

Si su cadena necesita incluir expansión variable, puede juntar cadenas entre comillas así:

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

Explicación

En bash $'string'provoca "expansión ANSI-C". Y eso es lo que la mayoría de nosotros esperamos cuando usamos cosas como \t, \r, \n, etc. Desde: https://www.gnu.org/software/bash/manual/html_node/ANSI_002dC-Quoting.html#ANSI_002dC-Quoting

Las palabras de la forma $ 'cadena' se tratan especialmente. La palabra se expande a una cadena , y los caracteres de escape con barra invertida se reemplazan según lo especificado por el estándar ANSI C. Las secuencias de escape de barra invertida, si están presentes, se decodifican ...

El resultado expandido es de comillas simples, como si el signo de dólar no hubiera estado presente.

Solución (si debe evitar bash)

Personalmente, creo que la mayoría de los esfuerzos para evitar bash son tontos porque evitar bashisms NO * hace que su código sea portátil. (Su código será menos frágil si lo manipula bash -euque si intenta evitar bash y usa sh[a menos que sea un ninja absoluto de POSIX]). Pero en lugar de tener un argumento religioso al respecto, le daré lo MEJOR * responder.

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

* ¿La mejor respuesta? Sí, porque un ejemplo de lo que la mayoría de los scripters de shell anti-bash harían mal en su código es usarlo echo '\t'como en la respuesta de @ robrecord . Eso funcionará para el eco de GNU, pero no para el eco de BSD. Eso lo explica The Open Group en http://pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html#tag_20_37_16 Y este es un ejemplo de por qué tratar de evitar los bashismos generalmente falla.

Bruno Bronosky
fuente
8

He usado algo como esto con un shell Bash en Ubuntu 12.04 (LTS):

Para agregar una nueva línea con tabulación, la segunda cuando la primera coincide:

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

Para reemplazar primero con tabulador, segundo :

sed -i 's/first/\\t second/g' filename
Thomas Bratt
fuente
4
El doble escape es clave, es decir, usar \\ty no \t.
Zamnuts
También tuve que usar comillas dobles en lugar de comillas simples en Ubuntu 16.04 y Bash 4.3.
caw
4

Utilice $(echo '\t'). Necesitará comillas alrededor del patrón.

P.ej. Para eliminar una pestaña:

sed "s/$(echo '\t')//"
robrecord
fuente
5
Es curioso que estés usando una característica específica de "GNU echo" (interpretando \ t como un carácter de tabulación) para resolver un error específico de "BSD sed" (interpretando \ t como 2 caracteres separados). Presumiblemente, si tiene "GNU echo" también tendrá "GNU sed". En cuyo caso no necesitaría utilizar echo. Con BSD, echo echo '\t'generará 2 caracteres separados. La forma portátil POSIX es usar printf '\t'. Por eso digo: No intente hacer que su código sea portátil sin usar bash. Es más difícil de lo que piensas. Usar bashes lo más portátil que la mayoría de nosotros podemos hacer.
Bruno Bronosky
3

No necesita usar sedpara hacer una sustitución cuando, en realidad, solo desea insertar una pestaña al frente de la línea. La sustitución de este caso es una operación costosa en comparación con simplemente imprimirlo, especialmente cuando se trabaja con archivos grandes. También es más fácil de leer ya que no es regex.

por ejemplo, usando awk

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

sedno admite \t, ni otras secuencias de escape como \npara el caso. La única forma que encontré para hacerlo fue insertar el carácter de tabulación en el script usando sed.

Dicho esto, es posible que desee considerar el uso de Perl o Python. Aquí hay un breve script de Python que escribí y que uso para todas las expresiones regulares de transmisión:

#!/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
fuente
2
Y la versión de Perl sería el shell de una sola línea "perl -pe 's / a / b /' filename" o "algo | perl -pe 's / a / b /'"
tiftik
0

En lugar de BSD sed, uso perl:

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

Creo que otros han aclarado esta adecuadas para determinar otros enfoques ( sed, AWK, etc.). Sin embargo, bashsiguen mis respuestas específicas (probadas en macOS High Sierra y CentOS 6/7).

1) Si OP quisiera usar un método de búsqueda y reemplazo similar al que propusieron originalmente, sugeriría usarlo perlpara esto, de la siguiente manera. Notas: las barras invertidas antes de los paréntesis para expresiones regulares no deberían ser necesarias, y esta línea de código refleja cómo $1es mejor usarla que \1con el perloperador de sustitución (por ejemplo, según la documentación de Perl 5 ).

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

2) Sin embargo, como lo señaló ghostdog74 , dado que la operación deseada es simplemente agregar una pestaña al comienzo de cada línea antes de cambiar el archivo tmp al archivo de entrada / destino ( $filename), lo recomendaría perlnuevamente pero con la siguiente modificación (s):

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

3) Por supuesto, el archivo tmp es superfluo , por lo que es mejor hacer todo 'en su lugar' (agregando -ibandera) y simplificar las cosas a una línea más elegante con

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