Чому sed не розпізнає \ t як вкладку?


105
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 $ ім'я файлу.
user2432405

Для користувача OS X (macOS) зверніться до цього питання .
Франклін Ю

Відповіді:


129

Не всі версії sedрозуміють \t. Просто замість цього вставте буквальну вкладку (натисніть Ctrl- Vпотім Tab).


2
Ага так; для уточнення: не всі версії sed розуміють \tу замінній частині виразу (вона добре розпізнається \tу частині, що відповідає шаблону)
John Weldon

3
awwwwwwwwwwwwwwwwwww, добре, що досить цікаво. І дивно. Чому б ти змусив його розпізнавати його в одному місці, а не в іншому ...?
шістдесят футів

2
Викликається зі сценарію, який не працює: sh вкладки ігноруються sh. Наприклад, наступний код із сценарію оболонки додасть $ TEXT_TO_ADD, не попереджуючи його за допомогою таблиці: sed "$ {LINE} a \\ $ TEXT_TO_ADD" $ FILE.
Дерексон

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'але не вистачало пояснень. Насправді я підозрюю, що через надзвичайно незручне використання ви, мабуть, маєте неповне розуміння (як це робить більшість із нас з басом). Дивіться моє пояснення нижче: stackoverflow.com/a/43190120/117471
Bruno Bronosky

1
Пам’ятайте, що BASH не розширюватиме змінні, як $TABусередині одиничних лапок, тому вам потрібно буде використовувати його подвійні лапки.
nealmcb

Обережно з використанням *внутрішніх подвійних лапок ... це трактуватиметься як глобус, а не як регулярний вираз.
levigroker

28

@sedit був на правильному шляху, але визначити змінну трохи незручно.

Рішення (специфічне для 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

Слова форми $ 'string " обробляються спеціально. Слово розширюється до рядка , а символи , що ухиляються від косої риски, замінюються відповідно до стандарту ANSI C. Послідовності втечі зворотного схилу, якщо вони є, декодуються ...

Розширений результат одноцитований, як ніби знак долара не був.

Рішення (якщо ви повинні уникати удару)

Я особисто думаю, що більшість зусиль, щоб уникнути баш, нерозумні, оскільки уникнення башизмів НЕ * робить ваш код портативним. (Ваш код буде менш крихким, якщо ви його шибангові, bash -euніж якщо ви намагаєтеся уникати баш і використовувати sh[якщо ви не абсолютний ніндзя POSIX]). * відповідь.

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

* НАЙКРАЩА відповідь? Так, тому що одним із прикладів того, що більшість скриптерів проти бомбних оболонок зробить неправильно у своєму коді, є використання, echo '\t'як у відповіді @ robrecord . Це буде працювати для відлуння GNU, але не для BSD. Це пояснюється 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
Подвійна втеча є ключовим, тобто використовувати, \\tа не використовувати \t.
замнуть

Також мені довелося використовувати подвійні лапки замість одинарних лапок на Ubuntu 16.04 та Bash 4.3.
каркати

4

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

Напр. Щоб видалити вкладку:

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

5
Смішно, що ви використовуєте специфічну функцію "ехо GNU" (інтерпретуючи \ t як символ вкладки) для вирішення конкретної помилки "BSD sed" (інтерпретуючи \ t як два окремих символи). Імовірно, якщо у вас є "GNU echo", ви також мали б "GNU sed". У такому випадку вам не потрібно буде використовувати ехо. За допомогою BSD відлуння echo '\t'буде виводити 2 окремих символи. Портативний спосіб POSIX - це використання printf '\t'. Ось чому я кажу: Не намагайтеся зробити свій код портативним, не використовуючи bash. Це важче, ніж ти думаєш. Використання bash- це найбільш портативна річ, яку може зробити більшість із нас.
Бруно Броноський

3

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

наприклад, з використанням awk

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


0

sedне підтримує \t, ані інші послідовності втечі, як \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
однофайл

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) Якщо ОП хотів би використовувати метод пошуку та заміни, подібний до того, що вони спочатку пропонували, то я б запропонував використовувати perlдля цього наступне. Примітки: зворотні риски перед дужками для регулярного вираження не повинні бути необхідними, і цей рядок коду відображає, як $1краще використовувати, ніж \1з perlоператором заміни (наприклад, в документації Perl 5 ).

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

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

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.