Подтвердить что ты не робот

С помощью? с sed

Я просто хочу получить номер файла, который может быть или не быть gzip'd. Однако, похоже, что регулярное выражение в sed не поддерживает ?. Вот что я пробовал:

echo 'file_1.gz'|sed -n 's/.*_\(.*\)\(\.gz\)?/\1/p'

и ничего не было возвращено. Затем я добавил ? к анализируемой строке:

echo 'file_1.gz?'|sed -n 's/.*_\(.*\)\(\.gz\)?/\1/p'

и получил:

1

Итак, похоже, что ?, используемый в большинстве регулярных выражений, не поддерживается в sed, верно? Хорошо, тогда мне бы хотелось, чтобы sed дал 1 для file_1 и file_1.gz. Какой лучший способ сделать это в bash script, если время выполнения критически важно?

4b9b3361

Ответ 1

Эквивалент x? равен \(x\|\).

Однако во многих версиях sed поддерживается опция включения расширенных регулярных выражений, которая включает ?. В GNU sed флаг -r. Обратите внимание, что это также изменяет неэкранированные парны для группировки. например:

echo 'file_1.gz'|sed -n -r 's/.*_(.*)(\.gz)?/\1/p'

На самом деле, есть еще одна ошибка в вашем регулярном выражении, которая заключается в том, что жадный .* в parens собирается усвоить ".gz", если таковой имеется. Насколько мне известно, у sed нет не-жадного эквивалента *, но вы можете использовать |, чтобы обойти это. | в sed (и во многих других реализациях регулярных выражений) будет использовать самое левое совпадение, которое работает, поэтому вы можете сделать что-то вроде этого:

echo 'file_1.gz'|sed -r 's/(.*_(.*)\.gz)|(.*_(.*))/\2\4/'

Это попытка сопоставления с .gz и только пытается без него, если это не работает. Только одна из групп 2 или 4 будет фактически существовать (поскольку они находятся на противоположных сторонах того же |), поэтому мы просто объединяем их, чтобы получить нужное значение.

Ответ 2

Если вы ищете ответ на конкретный пример, заданный в вопросе, или почему он неправильно использует ? (независимо от синтаксиса), см. ответ Лоуренса Гонсалвес.

Если вы ищете вместо этого ответ на общий вопрос о том, почему ? не проявляет своего особого значения в sed, как вы могли ожидать:

По умолчанию sed использует синтаксис основных синтаксических выражений POSIX, поэтому знак вопроса должен быть экранирован как \? для применения его специального значения, в противном случае он соответствует буквальному вопросительному знаку. В качестве альтернативы вы можете использовать опцию -r или --regexp-extended для использования "расширенного синтаксиса регулярных выражений", который отменяет значение экранированных и неэкранированных специальных символов, включая ?.

В словах документации GNU sed (просмотр по запуску "info sed" в Linux):

Единственное различие между основными и расширенными регулярными выражениями поведение нескольких символов: "?", "+", круглые скобки и фигурные скобки ( '{}'). Хотя основные регулярные выражения требуют, чтобы они были экранированы, если вы хотите, чтобы они вели себя как специальные символы при использовании расширенных регулярные выражения, вы должны избегать их, если хотите, чтобы они соответствовали буквальный символ.

и объясняется опция:

-r --regexp-extended

Используйте расширенные регулярные выражения, а не основные регулярные выражения выражения. Расширенные регулярные выражения - это те, которые `egrep 'принимает; они могут быть более ясными, поскольку они обычно имеют меньше обратных косых черт, но являются расширением GNU, и, следовательно, скрипты, которые их используют, не являются переносимым.

Ответ 3

echo 'file_1.gz'|sed -n 's/.*_\(.*\)\?\(\.gz\)/\1/p'

Работает. Вы должны вернуть его в нужное место, и вам нужно его избежать.

Ответ 4

Вы должны использовать awk, который превосходит sed, когда дело доходит до захвата/разбора полей:

$ awk -F'[._]' '{print $2}' <<<"file_1"
1
$ awk -F'[._]' '{print $2}' <<<"file_1.gz"
1

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

 var=file_1.gz; 
 temp=${var#*_}; 
 file=${temp%.*}
 echo $file

Примечание: работает, когда var=file_1 также

Ответ 5

Функция, которая должна возвращать число, которое следует за "_" в имени файла, независимо от расширения файла:

realname () {
  local n=${$1##*/}
  local rn="${n%.*}"
  sed 's/^.*\_//g' ${$rn:-$n}
}

Ответ 6

Часть решения заключается в выходе из вопросительного знака или с помощью опции -r.

sed 's/.*_\([^.]*\)\(\.\?[^.]\+\)\?$/\1/'

или

sed -r 's/.*_([^.]*)(\.?[^.]+)?$/\1/'

будет работать для:

file_1.gz
file_12.txt
file_123

в результате:

1
12
123

Ответ 7

Я просто понял, что может сделать что-то очень просто:

echo 'file_1.gz'|sed -n 's/.*_\([0-9]*\).*/\1/p'

Обратите внимание на [0-9]* вместо .*. @Laurence Gonsalves ответ заставил меня осознать жадность моего предыдущего поста.