Почему sed не распознает \ t как вкладку?

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

Я ожидаю, что этот sedскрипт вставит tabперед каждой строкой, $filenameно это не так. По какой-то причине tвместо него вставляется .

шестьдесят футов
источник
1
Поскольку sed может различаться между платформами (в частности, BSD / MacOSX и Linux), может быть полезно указать платформу, на которой вы используете sed.
Исаак
sed "s / (. *) / # \ 1 /" $ filename | tr '#' '\ t'> $ sedTmpFile && mv $ sedTmpFile $ filename.
user2432405
Для пользователей OS X (macOS) обратитесь к этому вопросу .
Франклин Ю

Ответы:

129

Не все версии sedпонимают \t. Вместо этого просто вставьте буквальную табуляцию (нажмите Ctrl- Vзатем Tab).

Марк Байерс
источник
2
О да; для уточнения: не все версии sed понимают \tзаменяющую часть выражения (она \tпрекрасно распознается в части сопоставления с образцом)
Джон Велдон,
3
Ой, хорошо, это довольно интересно. И странно. Почему вы заставляете его распознавать его в одном месте, а в другом - нет ...?
sixtyfootersdude
2
Вызывается из скрипта, это не сработает: вкладки будут игнорироваться sh. Например, следующий код из сценария оболочки добавит $ TEXT_TO_ADD без добавления табуляции перед ним: sed "$ {LINE} a \\ $ TEXT_TO_ADD" $ FILE.
Dereckson
2
@Dereckson и другие - см. Этот ответ: stackoverflow.com/a/2623007/48082
Cheeso
2
Дерексон с / может / не может /?
Дуглас Хелд
41

Используя Bash, вы можете программно вставить символ TAB следующим образом:

TAB=$'\t' 
echo 'line' | sed "s/.*/${TAB}&/g" 
echo 'line' | sed 's/.*/'"${TAB}"'&/g'   # use of Bash string concatenation
сидеть
источник
Это очень полезно.
Cheeso
1
Вы были на правильном пути, $'string'но без объяснения причин. На самом деле я подозреваю, что из-за чрезвычайно неудобного использования вы, вероятно, не полностью понимаете это (как большинство из нас делает с bash). См. Мое объяснение ниже: stackoverflow.com/a/43190120/117471
Бруно Броноски
1
Помните, что BASH не расширяет переменные, как $TABвнутри одинарных кавычек, поэтому вам нужно использовать двойные кавычки.
nealmcb 07
Будьте осторожны при использовании *внутри двойных кавычек ... это будет рассматриваться как глобус, а не как регулярное выражение, которое вы намереваетесь.
levigroker
28

@sedit был на правильном пути, но определять переменную немного неудобно.

Решение (специфично для bash)

В bash это можно сделать, поставив знак доллара перед строкой в ​​одиночных кавычках.

$ 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

Если ваша строка должна включать расширение переменных, вы можете объединить строки в кавычки следующим образом:

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

Объяснение

В bash $'string'вызывает "расширение ANSI-C". И это то , что большинство из нас ожидает , когда мы используем такие вещи , как \t, \r, \nи т.д. От: https://www.gnu.org/software/bash/manual/html_node/ANSI_002dC-Quoting.html#ANSI_002dC-Quoting

Особо обрабатываются слова вида $ 'строка' . Слово расширяется до строки заменяется с заменой экранированных символов обратной косой черты, как указано в стандарте ANSI C. Управляющие последовательности с обратной косой чертой, если они есть, декодируются ...

Расширенный результат заключен в одинарные кавычки, как если бы знака доллара не было.

Решение (если вы должны избегать bash)

Я лично считаю, что большинство попыток избежать bash глупо, потому что избегание bashisms НЕ * делает ваш код переносимым. (Ваш код будет менее хрупким, если вы его измените, bash -euчем если вы попытаетесь избежать bash и использовать sh[если вы не являетесь абсолютным ниндзя POSIX].) Но вместо того, чтобы иметь религиозные аргументы по этому поводу, я просто дам вам НАИЛУЧШЕЕ * ответ.

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

* Лучший ответ? Да, потому что один из примеров того, что большинство сценариев оболочки анти-bash сделали бы неправильно в своем коде, - это использование, echo '\t'как в ответе @ robrecord . Это будет работать для GNU echo, но не для BSD echo. Это объясняет The Open Group на http://pubs.opengroup.org/onlinepubs/9699919799/utilities/echo.html#tag_20_37_16. И это пример того, почему попытки избежать башизмов обычно терпят неудачу.

Бруно Броноски
источник
8

Я использовал что-то подобное с оболочкой Bash в Ubuntu 12.04 (LTS):

Чтобы добавить новую строку с табуляцией, вторая при совпадении первой :

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

Чтобы заменить первое на вкладку, второе :

sed -i 's/first/\\t second/g' filename
Томас Братт
источник
4
Двойной escape - это ключ, т.е. использование, \\tа не использование \t.
замнутс 06
Мне также пришлось использовать двойные кавычки вместо одинарных в Ubuntu 16.04 и Bash 4.3.
каркать
4

Используйте $(echo '\t'). Вам понадобятся цитаты вокруг выкройки.

Например. Чтобы удалить вкладку:

sed "s/$(echo '\t')//"
RobRecord
источник
5
Забавно, что вы используете особую функцию «GNU echo» (интерпретируете \ t как символ табуляции) для устранения специфической ошибки «BSD sed» (интерпретируя \ t как 2 отдельных символа). Предположительно, если у вас есть «GNU echo», у вас также будет «GNU sed». В этом случае вам не нужно использовать эхо. С BSD echo echo '\t'выводит 2 отдельных символа. Переносимый способ POSIX - использовать printf '\t'. Вот почему я говорю: не пытайтесь сделать свой код переносимым, не используя bash. Это сложнее, чем ты думаешь. Использование bash- это самая портативная вещь, которую может сделать большинство из нас.
Бруно Броноски
3

Вам не нужно использовать sed замену, когда на самом деле вы просто хотите вставить табуляцию перед строкой. Замена в этом случае - дорогостоящая операция по сравнению с простой распечаткой, особенно когда вы работаете с большими файлами. Его тоже легче читать, так как это не регулярное выражение.

например, используя awk

awk '{print "\t"$0}' $filename > temp && mv temp $filename
призрачная собака74
источник
0

sedне поддерживает \t, ни другие escape-последовательности, как \nв этом отношении. Единственный способ, который я нашел для этого, - это вставить символ табуляции в скрипт, используяsed .

Тем не менее, вы можете рассмотреть возможность использования Perl или Python. Вот небольшой сценарий Python, который я написал, который я использую для всех регулярных выражений потока:

#!/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
И версия Perl будет однострочным оболочкой «perl -pe 's / a / b /' filename» или «something | perl -pe 's / a / b /'»
tiftik
0

Вместо BSD sed я использую perl:

ct@MBA45:~$ python -c "print('\t\t\thi')" |perl -0777pe "s/\t/ /g"
   hi
Сиз Тиммерман
источник
0

Я думаю, что другие разъяснили это адекватно для других подходов ( sed, AWKи т. Д.). Тем не менее, следующие мои bashответы (проверенные на macOS High Sierra и CentOS 6/7).

1) Если OP хотел бы использовать метод поиска и замены, аналогичный тому, что они первоначально предлагали, я бы предложил использовать perlдля этого, как показано ниже. Примечания: обратная косая черта перед круглыми скобками для регулярного выражения не обязательна, и эта строка кода отражает, как $1лучше использовать, чем \1с perlоператором подстановки (например, согласно документации Perl 5 ).

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

2) Однако, как указано ghostdog74 , поскольку желаемая операция на самом деле состоит в том, чтобы просто добавить вкладку в начале каждой строки перед изменением файла tmp во входной / целевой файл ( $filename), я бы рекомендовал perlснова, но со следующей модификацией (s):

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

3) Конечно, файл tmp излишний , поэтому лучше просто сделать все «на месте» (добавить -iфлаг) и упростить задачу до более элегантного однострочника с

perl -i -pe $'s/^/\t/' $filename
Justincbagley
источник