Dlaczego sed nie rozpoznaje \ t jako karty?

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

Oczekuję, że ten sedskrypt wstawi znak tabprzed każdym wierszem, $filenameale tak nie jest. Z jakiegoś powodu tzamiast tego wstawia .

sixtyfootersdude
źródło
1
Ponieważ sed może różnić się między platformami (w szczególności BSD / MacOSX i Linux), pomocne może być określenie platformy, na której używasz seda.
Isaac,
sed "s / (. *) / # \ 1 /" $ nazwa_pliku | tr '#' '\ t'> $ sedTmpFile && mv $ sedTmpFile $ nazwa_pliku.
user2432405
W przypadku użytkowników systemu OS X (macOS) zapoznaj się z tym pytaniem .
Franklin Yu

Odpowiedzi:

129

Nie wszystkie wersje sedrozumieją \t. Po prostu wstaw zamiast tego dosłowną tabulator (naciśnij Ctrl- Va następnie Tab).

Mark Byers
źródło
2
O tak; wyjaśnienie: nie wszystkie wersje seda rozumieją \tw części zamiennej wyrażenia (rozpoznano ją \tw części pasującej do wzorca)
John Weldon
3
awwwwwwwwwwwwwwwwwww, ok, to całkiem interesujące. I dziwne. Dlaczego miałbyś go rozpoznać w jednym miejscu, a nie w drugim ...?
sixtyfootersdude
2
Wywołane ze skryptu, to nie zadziała: karty będą ignorowane przez sh. Na przykład poniższy kod ze skryptu powłoki doda $ TEXT_TO_ADD bez poprzedzania go tabelą: sed "$ {LINE} a \\ $ TEXT_TO_ADD" $ FILE.
Dereckson
2
@Dereckson i inni - zobacz tę odpowiedź: stackoverflow.com/a/2623007/48082
Cheeso
2
Dereckson s / can / can /?
Douglas odbył się
41

Używając Bash możesz wstawić znak TAB programowo, tak jak poniżej:

TAB=$'\t' 
echo 'line' | sed "s/.*/${TAB}&/g" 
echo 'line' | sed 's/.*/'"${TAB}"'&/g'   # use of Bash string concatenation
sedit
źródło
To jest bardzo pomocne.
Cheeso
1
Byłeś na dobrej drodze z $'string'wyjaśnieniem, ale brakuje ci. W rzeczywistości podejrzewam, ze względu na wyjątkowo niewygodne użycie, które prawdopodobnie masz niepełne zrozumienie (jak większość z nas robi z bashem). Zobacz moje wyjaśnienie poniżej: stackoverflow.com/a/43190120/117471
Bruno Bronosky
1
Pamiętaj, że BASH nie będzie rozszerzał zmiennych, tak jak $TABwewnątrz pojedynczych cudzysłowów, więc będziesz musiał używać go w podwójnych cudzysłowach.
nealmcb
Uważaj na używanie *podwójnych cudzysłowów ... będzie to traktowane jako glob, a nie jako wyrażenie regularne, które zamierzasz.
levigroker
28

@sedit był na właściwej ścieżce, ale definiowanie zmiennej jest trochę niewygodne.

Rozwiązanie (specyficzne dla bash)

Sposobem na zrobienie tego w bash jest umieszczenie znaku dolara przed łańcuchem w pojedynczym cudzysłowie.

$ 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

Jeśli twój ciąg musi zawierać rozwinięcie zmiennych, możesz umieścić łańcuchy w cudzysłowie w następujący sposób:

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

Wyjaśnienie

W bash $'string'powoduje "rozszerzenie ANSI-C". I to jest to, co większość z nas spodziewać, gdy używamy takich rzeczy \t, \r, \n, itd. Od: https://www.gnu.org/software/bash/manual/html_node/ANSI_002dC-Quoting.html#ANSI_002dC-Quoting

Słowa w postaci $ 'string' są traktowane specjalnie. Słowo jest interpretowane jako łańcuch , a znaki ze znakami ucieczki odwrotnym ukośnikiem są zastępowane zgodnie ze standardem ANSI C. Sekwencje ucieczki z ukośnikiem odwrotnym, jeśli są obecne, są dekodowane ...

Rozszerzony wynik jest podawany w apostrofy, tak jakby znak dolara nie był obecny.

Rozwiązanie (jeśli musisz unikać bash)

Osobiście uważam, że większość wysiłków mających na celu uniknięcie basha jest głupia, ponieważ unikanie bashizmu NIE * sprawia, że ​​kod jest przenośny. (Twój kod będzie mniej kruchy, jeśli go uderzysz, bash -euniż jeśli spróbujesz uniknąć basha i użyjesz sh[chyba że jesteś absolutnym ninja POSIX].) Ale zamiast dyskutować na ten temat religijnie, dam ci tylko NAJLEPSZE * odpowiedź.

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

* Najlepsza odpowiedź? Tak, ponieważ jednym z przykładów tego, co większość skryptów powłoki anty-bash zrobiłaby źle w swoim kodzie, jest użycie echo '\t'tak, jak w odpowiedzi @ robrecord . To zadziała dla echa GNU, ale nie echa BSD. Wyjaśnia to The Open Group na http://pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html#tag_20_37_16 I to jest przykład dlaczego próby uniknięcia bashizmów zwykle zawodzą.

Bruno Bronosky
źródło
8

Użyłem czegoś takiego z powłoką Bash na Ubuntu 12.04 (LTS):

Aby dołączyć nową linię z tabulatorem, druga po dopasowaniu pierwszej :

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

Aby wymienić pierwsze z zakładki, po drugie :

sed -i 's/first/\\t second/g' filename
Thomas Bratt
źródło
4
Kluczem jest podwójna ucieczka, tzn. Użyj, \\ta nie \t.
zamnuts
Musiałem także użyć podwójnych cudzysłowów zamiast pojedynczych cudzysłowów na Ubuntu 16.04 i Bash 4.3.
krakaj
4

Posługiwać się $(echo '\t') . Będziesz potrzebować cytatów wokół wzoru.

Na przykład. Aby usunąć kartę:

sed "s/$(echo '\t')//"
robrecord
źródło
5
To zabawne, że używasz specyficznej funkcji "GNU echo" (interpretując \ t jako znak tabulacji) do rozwiązania błędu specyficznego dla "BSD sed" (interpretując \ t jako 2 oddzielne znaki). Przypuszczalnie, jeśli masz „GNU echo”, miałbyś także „GNU sed”. W takim przypadku nie musisz używać echa. W przypadku BSD echo wypisze echo '\t'2 oddzielne znaki. Przenośnym sposobem POSIX jest użycie printf '\t'. Dlatego mówię: nie próbuj przenosić swojego kodu, nie używając basha. To trudniejsze niż myślisz. Używanie bashjest najbardziej przenośną rzeczą, jaką większość z nas może zrobić.
Bruno Bronosky
3

Nie musisz używać sed do podstawiania, gdy w rzeczywistości chcesz po prostu wstawić tabulator przed linią. Zastępowanie w tym przypadku jest kosztowną operacją w porównaniu do zwykłego drukowania, zwłaszcza podczas pracy z dużymi plikami. Jest też łatwiejszy do odczytania, ponieważ nie zawiera wyrażenia regularnego.

np. używając awk

awk '{print "\t"$0}' $filename > temp && mv temp $filename
ghostdog74
źródło
0

sednie obsługuje \t, ani innych sekwencji ucieczki, jak \nw tym przypadku. Jedynym sposobem, w jaki udało mi się to zrobić, było wstawienie znaku tabulacji do skryptu za pomocąsed .

To powiedziawszy, możesz rozważyć użycie Perla lub Pythona. Oto krótki skrypt w Pythonie, który napisałem, i którego używam do wszystkich regexów strumieniowych:

#!/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
źródło
2
Wersja Perla byłaby jednolinijkową powłoką „perl -pe 's / a / b /' nazwa_pliku” lub „coś | perl -pe 's / a / b /'”
tiftik
0

Zamiast seda z BSD używam perla:

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

Myślę, że inni wyjaśnić to odpowiednio do innych metod ( sed, AWK, itd.). Jednak moje bashodpowiedzi specyficzne (przetestowane na macOS High Sierra i CentOS 6/7) są dalej.

1) Jeśli OP chciałby użyć metody wyszukiwania i zamiany podobnej do tej, którą pierwotnie zaproponowali, sugerowałbym użycie perldo tego w następujący sposób. Uwagi: ukośniki odwrotne przed nawiasami dla wyrażenia regularnego nie powinny być konieczne, a ta linia kodu odzwierciedla, w jaki sposób $1lepiej jest używać niż \1z perloperatorem podstawienia (np. W dokumentacji Perl 5 ).

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

2) Jednak, jak wskazał ghostdog74 , ponieważ pożądaną operacją jest po prostu dodanie tabulatora na początku każdego wiersza przed zmianą pliku tmp na plik wejściowy / docelowy ( $filename), polecam perlponownie, ale z następującą modyfikacją (s):

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

3) Oczywiście plik tmp jest zbędny , więc lepiej po prostu zrobić wszystko `` na miejscu '' (dodając -iflagę) i uprościć wszystko do bardziej eleganckiej jednowierszowej

perl -i -pe $'s/^/\t/' $filename
justincbagley
źródło