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

sed "s/(.*)/t1/" $filename > $sedTmpFile && mv $sedTmpFile $filename

Je m'attends à ce que ce script sed insère un onglet dans la police de chaque ligne dans $filename mais ce n'est pas le cas. Pour une raison quelconque, il insère un t à la place.. Étrange..

75
demandé sur codeforester 2010-04-09 23:01:26

10 réponses

Toutes les versions de sed ne comprennent pas \t. Insérez simplement un onglet littéral à la place (appuyez sur Ctrl-V puis Tab).

93
répondu Mark Byers 2010-04-09 19:03:00

En utilisant Bash, vous pouvez insérer un caractère de tabulation par programmation comme ceci:

TAB=$'\t' 
echo 'line' | sed "s/.*/${TAB}&/g" 
echo 'line' | sed 's/.*/'"${TAB}"'&/g'   # use of Bash string concatenation
34
répondu sedit 2010-04-12 14:59:46

@sedit était sur la bonne voie, mais il est un peu difficile de définir une variable.

Solution (spécifique à bash)

La façon de le faire dans bash est d'utiliser mettre un signe dollar devant votre chaîne entre guillemets.

$ 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 expansion de variable, vous pouvez mettre des 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 "expansion ANSI-C". Et c'est ce que la plupart d'entre nous attendent quand 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

Mots de la forme $'chaîne de caractères' sont traités spécialement. Le mot se développe à chaîne , avec des caractères d'échappement antislash remplacés comme spécifié par le C ANSI standard. Les séquences d'échappement antislash, si elles sont présentes, sont décoder...

Le résultat étendu est entre guillemets simples, comme si le signe dollar n'avait pas être présent.

Solution (si vous devez éviter bash)

Personnellement, je pense que la plupart des efforts pour éviter le bash sont stupides car éviter les bashisms ne rend pas votre code portable. (Votre code sera moins fragile Si vous le shebang à bash -eu que si vous essayez d'éviter 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 la meilleure réponse*.

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

* meilleure réponse? Oui, parce qu'un exemple de ce que la plupart des scripteurs Shell anti-bash feraient mal dans leur code est d'utiliser echo '\t' comme dans la réponse de @robrecord . Cela fonctionnera pour GNU echo, mais pas BSD echo. Cela est expliqué par le groupe ouvert à http://pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html#tag_20_37_16 et c'est un exemple de pourquoi essayer d'éviter les bashisms échouent généralement.

11
répondu Bruno Bronosky 2018-09-06 18:33:52

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

Pour ajouter une nouvelle ligne avec onglet,deuxième lorsque premier est en correspondance:

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

Pour remplacer premier avec onglet,deuxième:

sed -i 's/first/\\t second/g' filename
7
répondu Thomas Bratt 2013-09-10 17:02:26

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

Par exemple. Pour supprimer un onglet:

sed "s/$(echo '\t')//"
4
répondu robrecord 2016-01-25 19:00:27

Vous n'avez pas besoin d'utiliser sed pour faire une substitution, alors qu'en réalité, vous voulez juste insérer une tabulation au début de la ligne. La Substitution pour ce cas est une opération coûteuse par rapport à l'impression, surtout lorsque vous travaillez avec de gros fichiers. C'est plus facile à lire aussi car ce n'est pas regex.

Par exemple en utilisant awk

awk '{print "\t"$0}' $filename > temp && mv temp $filename
2
répondu ghostdog74 2010-04-09 23:54:23

sed ne supporte pas \t, ni d'autres séquences d'échappement comme \n d'ailleurs. La seule façon que j'ai trouvée pour le faire était d'insérer réellement le caractère de tabulation dans le script en utilisant sed.

Cela dit, vous pouvez envisager d'utiliser Perl ou Python. Voici un court script Python que j'ai écrit que j'utilise pour tous les flux regex'ing:

#!/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:])
0
répondu Roman Nurik 2010-04-09 19:05:51

J'ai utilisé ceci sur Mac: -

sed -i '' $'$i\\\n\\\thello\n' filename

Utilisé ce lien pour référence

0
répondu Raj Hassani 2017-04-13 12:36:31

Au lieu de BSD sed, j'utilise perl:

ct@MBA45:~$ python -c "print('\t\t\thi')" |perl -0777pe "s/\t/ /g"
   hi
0
répondu Cees Timmerman 2017-08-15 22:36:10

Je pense que d'autres ont clarifié cela de manière adéquate pour d'autres approches (sed, AWK, etc.). Cependant, mes réponses spécifiques à bash (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'ils ont initialement proposé, alors je suggère d'utiliser perl pour cela, comme suit. Notes: les barres obliques inverses avant les parenthèses pour regex ne devraient pas être nécessaires, et cette ligne de code reflète comment $1 est préférable d'utiliser que \1 avec perl opérateur de substitution (par exemple par Perl 5 documentation).

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

2) Cependant, comme indiqué par ghostdog74 , puisque l'opération souhaitée consiste simplement à ajouter un onglet au début de chaque ligne avant de changer le fichier tmp en fichier d'entrée / cible ($filename), je recommanderais à nouveau perl mais avec les modifications suivantes:

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, donc c'est mieux de tout faire 'en place' (en ajoutant -i drapeau) et simplifier les choses à un plus élégant one-liner avec

perl -i -pe $'s/^/\t/' $filename
0
répondu justincbagley 2018-07-24 20:58:27