Как извлечь часть строки с помощью sed?

Я уже нашла подобный чехол проверьте В моем случае имя файла: backup_20240827000025.sql, мне нужно извлечь символы с помощью sed - 20240827. Для этой строки я пытаюсь выполнить sed с регулярным выражением в centos7 sed (GNU sed) 4.2.2.

string=backup_20240827000025.sql
echo $string | sed -r  's/\./[a-z]{6}\w\([0-9]{8}\)[0-9]+\.[a-z]{3}/\1/p'
Error:
sed: -e expression #1, char 49: invalid reference \1 on `s' command's RHS

Если я удалю \ обратную косую черту, окружающую круглые скобки

\./[a-z]{6}\w([0-9]{8})[0-9]+\.[a-z]{3},

затем я получаю свою строку обратно в том виде, в котором она была:

./backup_20240827000025.sql

Однако, если я использую Python и это регулярное выражение

'\./[a-z]{6}\w([0-9]{8})[0-9]+\.[a-z]{3}' 

он работает хорошо и выдает результат типа 20240827. Может ли кто-нибудь помочь с этим? Спасибо.

🤔 А знаете ли вы, что...
Bash поддерживает переменные, которые можно использовать для хранения данных и параметров скрипта.


51
3

Ответы:

Можете ли вы попробовать более простое регулярное выражение?

$ string=backup_20240827000025.sql
$ echo "$string" | sed -r 's/backup_([0-9]{8}).*/\1/'
20240827

Решено

Ваша входная строка не начинается с ./, поэтому ваше регулярное выражение вообще не соответствует. Аналогично, \* соответствует буквальной звездочке, но во входной строке ее нет.

Непонятно, зачем удваивать звездочки и прочие квантификаторы; это синтаксические ошибки.

\w не является переносимым, хотя, вероятно, поддерживается в CentOS.

С помощью простого, хорошо сформированного регулярного выражения вы получаете

bash$ echo "backup_20240827000025.sql" |
> sed -r -n 's/[a-z]{6}[^a-z0-9]*([0-9]{8})[0-9]+\.[a-z]{3}/\1/p'
20240827

Устранение ошибки ОП...

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

$ x=abcdef

$ sed 's/.*\(cd\).*/XX\1XX/' <<< "${x}"
XXcdXX

Если вы используете опцию -r, вам больше не нужно экранировать скобки (т. е. скобки рассматриваются как специальные символы), например:

$ sed -r 's/.*(cd).*/XX\1XX/' <<< "${x}"
XXcdXX

Фактически, если вы используете опцию -r, вам не нужно экранировать круглые скобки, заключающие в себя группу захвата. Когда вы экранируете скобки, вы сообщаете sed, что это буквальные скобки (т. е. не рассматриваете их как специальные символы). В случае OP комбинация -r и экранированных скобок оставляет команду sed без групп захвата, что, в свою очередь, означает, что обозначение \1 относится к несуществующей/недействительной ссылке:

$ sed -r 's/.*\(cd\).*/XX\1XX/' <<< "${x}"
      ^^      ^^  ^^     ^^
sed: -e expression #1, char 20: invalid reference \1 on `s' command's RHS

Итак, вызов OP sed должен выглядеть так:

sed -r 's/\./[a-z]{6}\w([0-9]{8})[0-9]+\.[a-z]{3}/\1/p'
                        ^        ^ ---- parents are not escaped

Но теперь ОП сталкивается с другими проблемами...

\./[a-z]{6} говорит, что мы ищем строку, состоящую из литералов . (точка) + / (косая черта) + [a-z]{6} (6 символов нижнего регистра). Но образцы данных OP не включают буквальные символы . + /, поэтому мы не видим никаких изменений:

$ echo "$string" | sed -r 's/\./[a-z]{6}\w([0-9]{8})[0-9]+\.[a-z]{3}/\1/p'
backup_20240827000025.sql

Наша первая попытка решить эту последнюю проблему — удалить \./, но затем мы столкнулись с новой проблемой:

$ echo "$string" | sed -r 's/[a-z]{6}\w([0-9]{8})[0-9]+\.[a-z]{3}/\1/p'
20240827
20240827

Двойной выход?

По умолчанию sed автоматически распечатает пространство шаблонов (первый 20240827), в то время как операция /p говорит о необходимости снова явно распечатать пространство шаблонов (2-й 20240827).

Чтобы ограничить вывод одной копией пространства шаблонов, у нас есть несколько вариантов:

######
# add '-n' to suppress automatic printing of the pattern space

echo "$string" | sed -r -n 's/[a-z]{6}\w([0-9]{8})[0-9]+\.[a-z]{3}/\1/p'
                        ^^
######
# or remove the 'p' operation

echo "$string" | sed -r 's/[a-z]{6}\w([0-9]{8})[0-9]+\.[a-z]{3}/\1/'

Оба из них будут генерировать единое пространство шаблонов:

20240827

Следует иметь в виду один момент, связанный с производительностью...

При передаче вывода одной команды на вторую команду оболочка запускает подпроцесс для второй команды. Создание подпроцесса требует относительно больших затрат ресурсов и времени и становится весьма заметным при создании чрезмерного количества подоболочек.

Хотя создание OP одной подоболочки не будет заметно в командной строке, хорошей практикой является выработать привычку удалять ненужные подоболочки, когда это возможно.

Это может заключаться в перестановке текущей команды или даже в использовании других функций оболочки.

Одна из идей заключалась в том, чтобы исключить ненужные echo в пользу строки здесь:

$ sed -r 's/[a-z]{6}\w([0-9]{8})[0-9]+\.[a-z]{3}/\1/' <<< "$string"
20240827                                              ^^^^^^^^^^^^^

В качестве альтернативы, поскольку OP, похоже, работает со строками фиксированной длины, мы можем использовать функцию bash's подстроки ("${var:start:length}"):

$ echo "${string:7:8}"

ПРИМЕЧАНИЕ: первый символ находится на позиции 0.

Если мы имеем дело с переменным количеством символов перед _, мы можем использовать подстановку параметра :

$ dt = "${string#*_}"                     # strip off leading <string1>_
$ typeset -p dt
declare -- dt = "20240827000025.sql"

$ echo "${dt:0:8}"                      # now use the substring feature
20240827

В примере OP мы просто хотим напечатать подстроку на стандартный вывод. Если OP необходимо зафиксировать подстроку в переменной, то у нас возникает еще одна проблема с производительностью...

Следующее также создаст подоболочку ($( command ... )):

$ newvar=$( sed -r 's/[a-z]{6}\w([0-9]{8})[0-9]+\.[a-z]{3}/\1/' <<< "$string" )
$ typeset -p newvar
declare -- newvar = "20240827"

Хорошей новостью является то, что мы можем повторно использовать решения по замене параметров и подстрокам, чтобы устранить эту дополнительную подоболочку.

$ newvar = "${string:7:8}"
$ typeset -p newvar
declare -- newvar = "20240827"

$ dt = "${string#*_}" 
$ newvar = "${dt:0:8}"
$ typeset -p newvar
declare -- newvar = "20240827"

Интересные вопросы для изучения