Структура команды sed:
s/— команда substitute (замена)[^ ]* \([^ ]*\).*— шаблон поиска (pattern)/— разделитель\1— замена (replacement)/— завершающий разделитель
Пошаговый разбор шаблона поиска:
1. [^ ]*
text
[^ ] - класс символов, НЕ пробел (любой символ кроме пробела) * - ноль или более повторений предыдущего элемента
Вместе: [^ ]* = «ноль или более любых символов, кроме пробела»
- В строке «Hello World Test» это соответствует «Hello»
2. (пробел)
text
- обычный пробел (литерал)
- Соответствует пробелу между «Hello» и «World»
3. \( и \)
text
\( ... \) - захватывающая группа (группировка с запоминанием)
- В sed скобки экранируются обратным слэшем для создания группы
- Содержимое группы будет доступно как
\1
4. [^ ]* внутри группы
text
Внутри \( ... \): [^ ]*
- То же самое: «ноль или более любых символов кроме пробела»
- В нашем случае соответствует «World»
- Это группа 1 (запомненная часть)
5. .*
text
. - любой одиночный символ (кроме перевода строки) * - ноль или более повторений
Вместе: .* = «ноль или более любых символов»
- В строке соответствует » Test» (пробел + «Test»)
Итоговое соответствие для «Hello World Test»:
text
Шаблон: [^ ]* \([^ ]*\) .*
| | | |
| | | " Test" (остаток строки)
| | "World" (группа 1)
| пробел
"Hello" (первое слово)
Полное соответствие: Hello World Test (вся строка)
Разбор части замены /\\1/:
\1— ссылка на первую захватывающую группу (то, что в первых\(...\))- В нашем случае группа 1 содержит
World
Как это работает:
- Находится вся строка по шаблону
- Из найденного извлекается группа 1 («World»)
- Вся найденная строка заменяется на содержимое группы 1
- Результат: «World»
Эквиваленты в других инструментах:
В awk (проще):
bash
echo "Hello World Test" | awk '{print $2}'
В sed с другими подходами:
bash
# Удалить все до первого пробела и после второго слова echo "Hello World Test" | sed 's/^[^ ]* //; s/ .*//' # Только второе слово echo "Hello World Test" | sed -E 's/^[^ ]+ ([^ ]+).*/\1/'
Визуализация процесса:
text
Исходная строка: "Hello World Test"
Шаблон поиска: /[^ ]* \([^ ]*\).*/
├─────┤│├─────┤├─┤
│ │││ ││ │
│ │││ ││ └─ .* = " Test"
│ │││ ││
│ │││ │└─ \) конец группы 1
│ │││ │
│ ││└─────┴─ [^ ]* = "World" (группа 1)
│ ││
│ │└─ \( начало группы 1
│ │
│ └─ пробел
│
└─ [^ ]* = "Hello"
Найдено: "Hello World Test"
Группа 1: "World"
Замена: s/найденное/группа1/
Результат: "World"
Этот паттерн находит первое слово + пробел + второе слово + всё остальное, и заменяет всё найденное на только второе слово.
подставив для тестов группу 2 или 0 то не получил hello и не получил, вот разбираем как
Ключевой момент:
В sed только то, что в \(...\) становится доступным как \1, \2 и т.д.
В нашем шаблоне: [^ ]* \([^ ]*\).*
\([^ ]*\)— это первая и единственная группа[^ ]*перед пробелом НЕ в скобках — не группа.*в конце НЕ в скобках — не группа
Ваши тесты объясняются:
1. \1 — работает
bash
echo "Hello World Test" | sed 's/[^ ]* \([^ ]*\).*/\1/' # Вывод: World
Потому что \1 ссылается на \([^ ]*\), что соответствует «World»
2. \2 — ошибка
bash
echo "Hello World Test" | sed 's/[^ ]* \([^ ]*\).*/\2/' # Ошибка: invalid reference \2
Потому что у нас только одна группа (\1). Группы \2 не существует.
3. \0 — особая история
bash
echo "Hello World Test" | sed 's/[^ ]* \([^ ]*\).*/\0/' # Вывод: Hello World Test
\0 в sed — это вся совпавшая строка, а не группа!
То есть шаблон нашел «Hello World Test», и \0 ссылается на всё это.
Чтобы получить Hello или Test, нужно изменить шаблон:
Получить «Hello» (первое слово):
bash
# Добавляем группу для первого слова echo "Hello World Test" | sed 's/\([^ ]*\) \([^ ]*\).*/\1/' # или echo "Hello World Test" | sed 's/^\([^ ]*\).*/\1/' # Вывод: Hello
Получить «Test» (третье слово):
bash
# Группа для третьего слова echo "Hello World Test" | sed 's/[^ ]* [^ ]* \([^ ]*\)/\1/' # или echo "Hello World Test" | sed 's/.* \([^ ]*\)$/\1/' # последнее слово # Вывод: Test
Пример с несколькими группами:
bash
echo "Hello World Test" | sed 's/\([^ ]*\) \([^ ]*\) \([^ ]*\)/\1-\2-\3/' # Вывод: Hello-World-Test # Группы: \1=Hello, \2=World, \3=Test echo "Hello World Test" | sed 's/\([^ ]*\) \([^ ]*\) \([^ ]*\)/\2/' # Вывод: World (вторая группа) echo "Hello World Test" | sed 's/\([^ ]*\) \([^ ]*\) \([^ ]*\)/\3/' # Вывод: Test (третья группа)
Таблица групп для разных шаблонов:
| Шаблон sed | Группы | Что доступно |
|---|---|---|
[^ ]* \([^ ]*\).* | \([^ ]*\) = 1 группа | \1 = «World» |
\([^ ]*\) [^ ]* [^ ]* | \([^ ]*\) = 1 группа | \1 = «Hello» |
[^ ]* [^ ]* \([^ ]*\) | \([^ ]*\) = 1 группа | \1 = «Test» |
\([^ ]*\) \([^ ]*\) \([^ ]*\) | 3 группы | \1=»Hello», \2=»World», \3=»Test» |
Правила sed для групп:
- Только
\(...\)создают нумерованные группы (\1,\2, …) \0— всегда вся совпавшая строка (даже без групп)- Нумерация групп идет по порядку открывающих
\( - Максимум 9 групп (
\1…\9) в старом sed
Ваши примеры с объяснением:
bash
# Исходный: [^ ]* \([^ ]*\).* # Только World в скобках -> только \1 # Чтобы получить Hello: sed 's/\([^ ]*\) .*/\1/' # Hello в скобках # Чтобы получить Test: sed 's/.* \([^ ]*\)$/\1/' # Test в скобках, .* до пробела # Чтобы получить всё кроме World: sed 's/\([^ ]*\) \([^ ]*\) \(.*\)/\1 \3/' # Hello Test
Практическое правило:
Если хотите что-то извлечь с помощью \номер в sed — оберните это в \(...\) в шаблоне поиска!
а что бы было без скобок , а какой в этих скобках еще может быть символ или символы и что они означают и может важен порядок этих символов [^ ]* text [^ ] — класс символов, НЕ пробел (любой символ кроме пробела)
разберем что внутри [ ] и что означает конструкция без скобок.
1. Без скобок \(...\)
Без захватывающих скобок:
bash
echo "Hello World Test" | sed 's/[^ ]* [^ ]* .*/REPLACEMENT/' # Находит: "Hello World Test" # Заменяет ВСЮ строку на "REPLACEMENT"
Без \(...\) вы не можете использовать \1, \2 и т.д.
Можете использовать только специальные символы:
&— вся найденная строка\0— то же самое что&(вся найденная строка)
Пример:
bash
echo "Hello World Test" | sed 's/[^ ]* [^ ]* .*/& &/' # Вывод: Hello World Test Hello World Test # & = вся найденная строка echo "Hello World Test" | sed 's/[^ ]* [^ ]* .*/Found: &/' # Вывод: Found: Hello World Test
2. Что внутри [^ ] — это КЛАСС СИМВОЛОВ
Конструкция [...] означает «любой ОДИН символ из перечисленных»
Варианты классов символов:
Базовые:
bash
[abc] # любой из символов: a, b, или c [a-z] # любой символ от a до z [A-Z] # любой символ от A до Z [0-9] # любая цифра [a-zA-Z] # любая буква (латиница)
Специальные внутри [ ]:
bash
[^abc] # НЕ a, НЕ b, НЕ c (любой символ кроме этих) [-.] # дефис или точка (дефис в начале/конце не диапазон) [\]\[] # экранированные квадратные скобки [.*+?] # метасимволы теряют спец. значение внутри [ ]
Ваш случай [^ ]:
bash
[^ ] # любой символ КРОМЕ пробела
# ^ внутри [ ] означает отрицание/инверсию
# пробел - просто символ пробела
3. Что означает [^ ]*:
Разберем по частям:
text
[^ ] - класс: "любой один символ кроме пробела" * - квантификатор: "ноль или более повторений предыдущего"
Вместе: [^ ]* = «ноль или более любых символов кроме пробела»
Примеры соответствия:
bash
"" # соответствует (ноль символов) "a" # соответствует "abc" # соответствует "123" # соответствует "a-b" # соответствует "a b" # НЕ соответствует (есть пробел!)
4. Порядок символов ВНУТРИ [ ]
НЕ важен для большинства символов:
bash
[abc] ≡ [bac] ≡ [cba] # одинаково
ВАЖЕН для:
- Диапазонов
a-z,0-9:bash[a-z] # от a до z (корректно) [z-a] # ошибка или undefined behavior [0-9] # от 0 до 9 (корректно) [9-0] # ошибка - Специальных символов:bash[]-] # ] или — (специальные символы в определенных позициях) [^] # если ^ не первый символ — просто каретка [^-] # ^ первый = отрицание, — не первый = просто дефис
Правила позиционирования внутри [ ]:
text
[^abc] # ^ ПЕРВЫЙ символ = отрицание класса [a^bc] # ^ НЕ первый = просто символ каретки [a-c] # - между символами = диапазон [-ac] # - первый = просто дефис [ac-] # - последний = просто дефис [a\-c] # \- экранированный = просто дефис
5. Примеры с пробелом в классе символов:
bash
# Пробел как обычный символ внутри [] [abc ] # a, b, c, или пробел [a-z ] # любая буква ИЛИ пробел [^ ] # НЕ пробел (ваш случай) [^a-z ] # НЕ буква и НЕ пробел [[:space:]] # POSIX класс: любой пробельный символ (пробел, таб, и т.д.)
6. Сравнение: со скобками \(...\) и без
С захватывающими скобками (группа):
bash
echo "Hello World" | sed 's/\([^ ]*\) \([^ ]*\)/\2 \1/' # Находит: "Hello World" # Группа 1 (\1): "Hello" # Группа 2 (\2): "World" # Замена: "\2 \1" = "World Hello" # Результат: "World Hello"
Без захватывающих скобок:
bash
echo "Hello World" | sed 's/[^ ]* [^ ]*/Replacement/' # Находит: "Hello World" # Нет групп # Заменяет на "Replacement" # Результат: "Replacement" # Или с & (вся найденная строка): echo "Hello World" | sed 's/[^ ]* [^ ]*/Before & After/' # Результат: "Before Hello World After"
7. Практическая разница на примере:
Исходная строка: "John 25 Engineer"
С группами (можно переставлять части):
bash
echo "John 25 Engineer" | sed 's/\([^ ]*\) \([^ ]*\) \([^ ]*\)/\3: \1 (\2 years)/' # Вывод: "Engineer: John (25 years)" # \1=John, \2=25, \3=Engineer
Без групп (только вся строка):
bash
echo "John 25 Engineer" | sed 's/[^ ]* [^ ]* [^ ]*/& - processed/' # Вывод: "John 25 Engineer - processed" # & = "John 25 Engineer"
8. Важные отличия:
| Аспект | Без \(...\) | С \(...\) |
|---|---|---|
| Можно ссылаться | только на всю строку (& или \0) | на части (\1, \2, …) |
| Использование | простая замена | извлечение, перестановка, сложные замены |
| Память | не запоминает части | запоминает части для повторного использования |
| Пример замены | s/pattern/replacement/ | s/\(part1\) \(part2\)/\2 \1/ |
9. Еще пример с [^ ]*:
bash
# Файл с данными: # Name Age City # John 25 NY # Alice 30 LA # Без групп - просто удалить первые 2 слова: sed 's/[^ ]* [^ ]* //' file.txt # Вывод: # City # NY # LA # С группами - извлечь только возраст (второе слово): sed 's/[^ ]* \([^ ]*\) [^ ]*/\1/' file.txt # Вывод: # Age # 25 # 30
Ключевой вывод:[^ ]* = «слово» (последовательность непробельных символов)\(...\) = «запомнить эту часть для использования в замене»
Без \(...\) вы не можете манипулировать отдельными частями найденного текста.
Задачки от простых к сложным
Фразы-ключи для запоминания:
[^ ]= «НЕ пробел» (один любой символ кроме пробела)[^ ]*= «слово» (ноль или больше не-пробелов)\(...\)= «запомни это» (создает группу)\1,\2= «вспомни 1-е, 2-е» (ссылки на группы)&или\0= «всё найденное» (вся совпавшая строка)[abc]= «или a, или b, или c» (класс символов)[a-z]= «от a до z» (диапазон символов)[^abc]= «НЕ a, НЕ b, НЕ c» (отрицание класса)
ЗАДАЧИ
Уровень 1: Базовый (понимание [^ ]*)
Задача 1.1:
Из строки «apple banana cherry» извлеките только первое слово
bash
echo "apple banana cherry" | sed 's/______/______/' # Ожидаемый вывод: apple
Задача 1.2:
Из строки «John Doe» удалите первое слово
bash
echo "John Doe" | sed 's/______/______/' # Ожидаемый вывод: Doe
Задача 1.3:
Замените всё после первого слова на «…»
bash
echo "Hello beautiful world" | sed 's/______/______/' # Ожидаемый вывод: Hello...
Уровень 2: Группы \(...\)
Задача 2.1:
Поменяйте местами «cat» и «dog» в строке «cat dog»
bash
echo "cat dog" | sed 's/______/______/' # Ожидаемый вывод: dog cat
Задача 2.2:
Из «Name: Alice Age: 25» извлеките только возраст
bash
echo "Name: Alice Age: 25" | sed 's/______/______/' # Ожидаемый вывод: 25
Задача 2.3:
Преобразуйте «file.txt» в «txt.file»
bash
echo "file.txt" | sed 's/______/______/' # Ожидаемый вывод: txt.file
Уровень 3: Классы символов [ ]
Задача 3.1:
Удалите все цифры из строки
bash
echo "abc123def456" | sed 's/______/______/' # Ожидаемый вывод: abcdef
Задача 3.2:
Оставьте только буквы (удалите всё кроме a-z, A-Z)
bash
echo "Hello123 World!@#" | sed 's/______/______/g' # Ожидаемый вывод: HelloWorld
Задача 3.3:
Из «ip: 192.168.1.1» извлеките только IP-адрес
bash
echo "ip: 192.168.1.1" | sed 's/______/______/' # Ожидаемый вывод: 192.168.1.1
Уровень 4: Комбинирование
Задача 4.1:
Из «user=admin role=super» извлеките значение role
bash
echo "user=admin role=super" | sed 's/______/______/' # Ожидаемый вывод: super
Задача 4.2:
Преобразуйте дату «2024-05-20» в «20/05/2024»
bash
echo "2024-05-20" | sed 's/______/______/' # Ожидаемый вывод: 20/05/2024
Задача 4.3:
Из «ERROR 404: Not Found» извлеките код ошибки
bash
echo "ERROR 404: Not Found" | sed 's/______/______/' # Ожидаемый вывод: 404
Уровень 5: Сложные (реальные кейсы)
Задача 5.1:
Из лог-строки извлеките IP-адрес
bash
echo "192.168.1.1 - admin [20/May/2024:10:30:45] GET /index.html" | sed 's/______/______/' # Ожидаемый вывод: 192.168.1.1
Задача 5.2:
Поменяйте формат имени «Doe, John» → «John Doe»
bash
echo "Doe, John" | sed 's/______/______/' # Ожидаемый вывод: John Doe
Задача 5.3:
Извлеките расширение файла из полного пути
bash
echo "/home/user/docs/report.pdf" | sed 's/______/______/' # Ожидаемый вывод: pdf
Уровень 6: Экспертный
Задача 6.1:
Разделить строку на три группы и вывести в обратном порядке
bash
echo "one two three" | sed 's/______/______/' # Ожидаемый вывод: three two one
Задача 6.2:
Из «CPU: 45%, RAM: 78%, DISK: 23%» извлеките все три значения
bash
echo "CPU: 45%, RAM: 78%, DISK: 23%" | sed 's/______/______/' # Ожидаемый вывод: 45 78 23
Задача 6.3:
Удалить всё внутри круглых скобок (включая скобки)
bash
echo "Hello (remove this) World (and this too)" | sed 's/______/______/g' # Ожидаемый вывод: Hello World
ПОДСКАЗКИ ДЛЯ САМОПРОВЕРКИ:
Для уровня 1:
- Используйте
[^ ]*для «слова» s/pattern/replacement/без групп
Для уровня 2:
- Оберните то, что нужно сохранить, в
\(...\) - Используйте
\1,\2для ссылок
Для уровня 3:
[0-9]для цифр[a-zA-Z]для букв[^ ]для «не пробел»- Добавьте
gв конце для глобальной замены
Для уровня 4:
- Комбинируйте
[^ ]*с группами - Используйте конкретные символы как разделители
Для уровня 5:
- Учитывайте специфичные форматы (IP, даты, пути)
- Используйте более точные шаблоны
Для уровня 6:
- Множественные группы
\(...\) - Специальные символы:
\.для точки,\s*для пробелов - Флаги вроде
gдля повторной замены
ПРИМЕР РЕШЕНИЯ (чтобы понять формат):
Простая задача: Из «Hello World» получить «World»
bash
# Решение: echo "Hello World" | sed 's/[^ ]* \([^ ]*\)/\1/' # или echo "Hello World" | sed 's/^[^ ]* //'
Попробуйте