Pourquoi sed ne reconnaît-il pas \ t comme un onglet?


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

Je m'attends à ce que ce sedscript insère un tabdevant chaque ligne, $filenamemais ce n'est pas le cas. Pour une raison quelconque, il insère un tfichier.


1
Comme sed peut varier entre les plates-formes (en particulier, BSD / MacOSX par rapport à Linux), il peut être utile de spécifier la plate-forme sur laquelle vous utilisez sed.
Isaac

sed "s / (. *) / # \ 1 /" $ filename | tr '#' '\ t'> $ sedTmpFile && mv $ sedTmpFile $ filename.
user2432405

Pour les utilisateurs d'OS X (macOS), reportez-vous à cette question .
Franklin Yu

Réponses:


129

Toutes les versions de sedcomprennent \t. Insérez simplement une tabulation littérale à la place (appuyez sur Ctrl- Vpuis Tab).


2
Ah oui; pour clarifier: toutes les versions de sed ne comprennent pas \tdans la partie de remplacement de l'expression (il est reconnu \tdans la partie de correspondance de motif très bien)
John Weldon

3
awwwwwwwwwwwwwwwwwww, ok c'est assez intéressant. Et étrange. Pourquoi voudriez-vous le faire reconnaître à un endroit mais pas à l'autre ...?
sixtyfootersdude

2
Appelé à partir d'un script, cela ne fonctionnera pas: les onglets seraient ignorés par sh. Par exemple, le code suivant d'un script shell ajoutera $ TEXT_TO_ADD, sans le faire précéder d'une tabulation: sed "$ {LINE} a \\ $ TEXT_TO_ADD" $ FILE.
Dereckson

2
@Dereckson et autres - voir cette réponse: stackoverflow.com/a/2623007/48082
Cheeso

2
Dereckson s / peut / ne peut pas /?
Douglas a tenu le

41

En utilisant Bash, vous pouvez insérer un caractère TAB par programme comme ceci:

TAB=$'\t' 
echo 'line' | sed "s/.*/${TAB}&/g" 
echo 'line' | sed 's/.*/'"${TAB}"'&/g'   # use of Bash string concatenation

C'est très utile.
Cheeso

1
Vous étiez sur la bonne voie avec l' $'string'explication mais manquez. En fait, je soupçonne, en raison de l'utilisation extrêmement maladroite que vous avez probablement une compréhension incomplète (comme la plupart d'entre nous le font avec bash). Voir mon explication ci-dessous: stackoverflow.com/a/43190120/117471
Bruno Bronosky

1
N'oubliez pas que BASH ne développera pas les variables comme $TABles guillemets simples, vous devrez donc les utiliser entre guillemets doubles.
nealmcb

Attention à utiliser *des guillemets doubles à l'intérieur ... cela sera traité comme un glob, pas comme l'expression régulière que vous souhaitez.
levigroker

28

@sedit était sur le bon chemin, mais il est un peu difficile de définir une variable.

Solution (spécifique à bash)

La façon de faire cela dans bash est de mettre un signe dollar devant votre chaîne entre guillemets 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

Si votre chaîne doit inclure une extension de variable, vous pouvez mettre les chaînes entre guillemets comme ceci:

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

Explication

Dans bash $'string'provoque "l'expansion ANSI-C". Et c'est - ce que la plupart d' entre nous attendent lorsque nous utilisons des choses comme \t, \r, \n, etc. De: https://www.gnu.org/software/bash/manual/html_node/ANSI_002dC-Quoting.html#ANSI_002dC-Quoting

Les mots de la forme $ 'string' sont traités spécialement. Le mot se développe en chaîne , avec les caractères d'échappement par barre oblique inverse remplacés comme spécifié par la norme ANSI C. Les séquences d'échappement de backslash, si présentes, sont décodées ...

Le résultat développé est entre guillemets simples, comme si le signe dollar n’était pas présent.

Solution (si vous devez éviter le bash)

Personnellement, je pense que la plupart des efforts pour éviter les bash sont ridicules, car éviter les bashismes ne rend PAS votre code portable. (Votre code sera moins fragile si vous le limitez bash -euque si vous essayez d'éviter de bash et d'utiliser sh[sauf si vous êtes un ninja POSIX absolu].) Mais plutôt que d'avoir un argument religieux à ce sujet, je vais juste vous donner le MEILLEUR * répondre.

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

* Meilleure réponse? Oui, car un exemple de ce que la plupart des scripts shell anti-bash feraient de mal dans leur code est l'utilisation echo '\t'comme dans la réponse de @ robrecord . Cela fonctionnera pour l'écho GNU, mais pas pour l'écho BSD. Cela est expliqué par The Open Group à http://pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html#tag_20_37_16 Et ceci est un exemple de pourquoi essayer d'éviter les bashismes échoue généralement.


8

J'ai utilisé quelque chose comme ça avec un shell Bash sur Ubuntu 12.04 (LTS):

Pour ajouter une nouvelle ligne avec tabulation, en second lieu lorsque la première correspond:

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

Pour remplacer d' abord par tabulation, ensuite :

sed -i 's/first/\\t second/g' filename

4
Le double échappement est clé, c'est-à-dire utiliser \\tet non \t.
zamnuts

J'ai également dû utiliser des guillemets doubles au lieu de guillemets simples sur Ubuntu 16.04 et Bash 4.3.
caw

4

Utilisez $(echo '\t'). Vous aurez besoin de citations autour du motif.

Par exemple. Pour supprimer un onglet:

sed "s/$(echo '\t')//"

5
C'est drôle que vous utilisiez une fonctionnalité spécifique "GNU echo" (interprétant \ t comme un caractère de tabulation) pour résoudre un bogue spécifique "BSD sed" (interprétant \ t comme 2 caractères séparés). Vraisemblablement, si vous avez "GNU echo", vous auriez aussi "GNU sed". Dans ce cas, vous n'aurez pas besoin d'utiliser echo. Avec BSD, l'écho echo '\t'va produire 2 caractères séparés. Le moyen portable POSIX est d'utiliser printf '\t'. C'est pourquoi je dis: n'essayez pas de rendre votre code portable en n'utilisant pas bash. C'est plus dur que tu ne le penses. L'utilisation bashest la chose la plus portable que la plupart d'entre nous puissent faire.
Bruno Bronosky

3

Vous n'avez pas besoin d'utiliser sedpour faire une substitution alors qu'en fait, vous voulez simplement insérer une tabulation devant la ligne. La substitution pour ce cas est une opération coûteuse par rapport à la simple impression, surtout lorsque vous travaillez avec de gros fichiers. C'est plus facile à lire car ce n'est pas une expression régulière.

par exemple en utilisant awk

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


0

sedne prend pas en charge \t, ni d'autres séquences d'échappement comme \nd'ailleurs. Le seul moyen que j'ai trouvé pour le faire était d'insérer le caractère de tabulation dans le script en utilisantsed .

Cela dit, vous pouvez envisager d'utiliser Perl ou Python. Voici un court script Python que j'ai écrit et que j'utilise pour toutes les expressions rationnelles de flux:

#!/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:])

2
Et la version Perl serait le shell one-liner "perl -pe 's / a / b /' filename" ou "quelque chose | perl -pe 's / a / b /'"
tiftik

0

Au lieu de BSD sed, j'utilise perl:

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

0

Je pense que d' autres ont permis de clarifier ce suffisamment pour d' autres approches ( sed, AWK, etc.). Cependant, mes bashréponses spécifiques (testées sur macOS High Sierra et CentOS 6/7) suivent.

1) Si OP voulait utiliser une méthode de recherche et de remplacement similaire à ce qu'il avait proposé à l'origine, je suggérerais d'utiliser perlpour cela, comme suit. Notes: les barres obliques inverses avant les parenthèses pour les regex ne devraient pas être nécessaires, et cette ligne de code reflète comment il $1est préférable de l'utiliser \1qu'avec perlun opérateur de substitution (par exemple, selon la documentation Perl 5 ).

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

2) Cependant, comme l'a souligné ghostdog74 , puisque l'opération souhaitée est en fait d' ajouter simplement un onglet au début de chaque ligne avant de changer le fichier tmp en fichier d'entrée / cible ( $filename), je recommanderais à perlnouveau mais avec la modification suivante (s):

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

3) Bien sûr, le fichier tmp est superflu , il est donc préférable de tout faire `` en place '' (ajouter un -idrapeau) et de simplifier les choses en un one-liner plus élégant avec

perl -i -pe $'s/^/\t/' $filename
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.