Perché sed non riconosce \ t come una scheda?


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

Mi aspetto che questo sedscript inserisca un tabdavanti a ogni riga, $filenamema non lo è. Per qualche motivo sta inserendo tinvece un file .


1
Poiché sed può variare tra le piattaforme (in particolare, BSD / MacOSX rispetto a Linux), può essere utile specificare la piattaforma su cui stai usando sed.
Isaac

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

Per gli utenti OS X (macOS), fare riferimento a questa domanda .
Franklin Yu

Risposte:


129

Non tutte le versioni di sedcapire \t. Basta inserire invece una tabulazione letterale (premere Ctrl- Vquindi Tab).


2
Ah sì; per chiarire: non tutte le versioni di sed capiscono \tnella parte sostitutiva dell'espressione (riconosciuta \tnella parte del pattern matching benissimo)
John Weldon

3
awwwwwwwwwwwwwwwwwww, ok è piuttosto interessante. E strano. Perché dovresti farlo riconoscere in un posto ma non nell'altro ...?
sixtyfootersdude

2
Chiamato da uno script, non funzionerà: le tabulazioni verrebbero ignorate da sh. Ad esempio, il codice seguente da uno script di shell aggiungerà $ TEXT_TO_ADD, senza anteporlo a una tabulazione: sed "$ {LINE} a \\ $ TEXT_TO_ADD" $ FILE.
Dereckson

2
@Dereckson e altri - vedono questa risposta: stackoverflow.com/a/2623007/48082
Cheeso

2
Dereckson s / può / non può /?
Douglas Held

41

Usando Bash puoi inserire un carattere di tabulazione a livello di codice in questo modo:

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

Questo è molto utile.
Cheeso

1
Eri sulla strada giusta con la $'string'spiegazione ma manca. In effetti sospetto, a causa dell'uso estremamente imbarazzante che probabilmente hai una comprensione incompleta (come la maggior parte di noi fa con bash). Vedere la mia spiegazione di seguito: stackoverflow.com/a/43190120/117471
Bruno Bronosky

1
Ricorda che BASH non espanderà variabili come le $TABvirgolette singole, quindi dovrai usarle virgolette doppie.
nealmcb

Attento all'uso di *virgolette doppie ... questo verrà trattato come un glob, non come la regex che intendi.
levigroker

27

@sedit era sulla strada giusta, ma è un po 'imbarazzante definire una variabile.

Soluzione (specifica per bash)

Il modo per farlo in bash è mettere un segno di dollaro davanti alla singola stringa tra virgolette.

$ 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 la tua stringa deve includere l'espansione della variabile, puoi mettere insieme le stringhe tra virgolette in questo modo:

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

Spiegazione

In bash $'string'causa "l'espansione ANSI-C". E questo è ciò che la maggior parte di noi si aspettano quando usiamo le cose come \t, \r, \n, ecc Da: https://www.gnu.org/software/bash/manual/html_node/ANSI_002dC-Quoting.html#ANSI_002dC-Quoting

Le parole nella forma $ "stringa" vengono trattate in modo speciale. La parola si espande in stringa , con caratteri di escape con barra rovesciata sostituiti come specificato dallo standard ANSI C. Le sequenze di escape backslash, se presenti, vengono decodificate ...

Il risultato espanso è a virgolette singole, come se il segno del dollaro non fosse presente.

Soluzione (se devi evitare bash)

Personalmente penso che la maggior parte degli sforzi per evitare il bash siano sciocchi perché evitare i bashismi NON * rende il tuo codice portabile. (Il tuo codice sarà meno fragile se lo fai, bash -eupiuttosto che se cerchi di evitare bash e usare sh[a meno che tu non sia un ninja POSIX assoluto].) Ma invece di avere una discussione religiosa su questo, ti darò solo il MEGLIO * risposta.

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

* Migliore risposta? Sì, perché un esempio di ciò che la maggior parte degli script di shell anti-bash farebbe di sbagliato nel loro codice è l'uso echo '\t'come nella risposta di @ robrecord . Questo funzionerà per GNU echo, ma non per BSD echo. Ciò è spiegato da The Open Group su http://pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html#tag_20_37_16 E questo è un esempio del perché il tentativo di evitare i bashismi di solito fallisce.


8

Ho usato qualcosa di simile con una shell Bash su Ubuntu 12.04 (LTS):

Per aggiungere una nuova riga con tabulazione, la seconda quando viene trovata la prima :

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

Per sostituire il primo con la tabulazione, il secondo :

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

4
La doppia fuga è fondamentale, ovvero usare \\te non \t.
Zamnuts

Ho anche dovuto usare virgolette doppie invece di virgolette singole su Ubuntu 16.04 e Bash 4.3.
caw

4

Usa $(echo '\t'). Avrai bisogno di virgolette attorno allo schema.

Per esempio. Per rimuovere una scheda:

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

5
È divertente che tu stia usando una caratteristica specifica "GNU echo" (interpretando \ t come un carattere di tabulazione) per risolvere un bug specifico "BSD sed" (interpretando \ t come 2 caratteri separati). Presumibilmente, se hai "GNU echo" dovresti anche avere "GNU sed". In tal caso non sarà necessario utilizzare l'eco. Con BSD echo echo '\t'produrrà 2 caratteri separati. Il modo portatile POSIX è usare printf '\t'. Questo è il motivo per cui dico: non cercare di rendere portabile il tuo codice non usando bash. È più difficile di quanto pensi. L'utilizzo bashè la cosa più portatile che la maggior parte di noi può fare.
Bruno Bronosky

3

Non è necessario utilizzare sedper eseguire una sostituzione quando in realtà, si desidera semplicemente inserire una tabulazione davanti alla riga. La sostituzione per questo caso è un'operazione costosa rispetto alla semplice stampa, soprattutto quando si lavora con file di grandi dimensioni. È anche più facile da leggere perché non è regex.

es. usando awk

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


0

sednon supporta \t, né altre sequenze di escape come \nper quella materia. L'unico modo che ho trovato per farlo è stato inserire effettivamente il carattere di tabulazione nello script usando sed.

Detto questo, potresti prendere in considerazione l'utilizzo di Perl o Python. Ecco un breve script Python che ho scritto che uso per tutte le espressioni regolari di flusso:

#!/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
E la versione Perl sarebbe la shell one-liner "perl -pe 's / a / b /' filename" o "qualcosa | perl -pe 's / a / b /'"
tiftik

0

Invece di BSD sed, io uso perl:

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

0

Credo che altri hanno chiarito questo adeguatamente per altri approcci ( sed, AWK, ecc). Tuttavia, bashseguono le mie risposte specifiche (testate su macOS High Sierra e CentOS 6/7).

1) Se OP volesse utilizzare un metodo di ricerca e sostituzione simile a quello originariamente proposto, allora suggerirei di utilizzare perlper questo, come segue. Note: i backslash prima delle parentesi per la regex non dovrebbero essere necessari, e questa riga di codice riflette come $1è meglio usarla che \1con l' perloperatore di sostituzione (ad esempio per la documentazione di Perl 5 ).

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

2) Tuttavia, come sottolineato da ghostdog74 , poiché l'operazione desiderata è in realtà quella di aggiungere semplicemente una scheda all'inizio di ogni riga prima di cambiare il file tmp nel file di input / target ( $filename), lo consiglierei di perlnuovo ma con la seguente modifica (S):

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

3) Ovviamente, il file tmp è superfluo , quindi è meglio fare tutto "a posto" (aggiungendo una -ibandiera) e semplificare le cose a una riga più elegante con

perl -i -pe $'s/^/\t/' $filename
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.