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..
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).
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
@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.
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
Utilisez $(echo '\t')
. Vous aurez besoin de citations autour du modèle.
Par exemple. Pour supprimer un onglet:
sed "s/$(echo '\t')//"
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
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:])
Au lieu de BSD sed, j'utilise perl:
ct@MBA45:~$ python -c "print('\t\t\thi')" |perl -0777pe "s/\t/ /g"
hi
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