Используете? с седом

20

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

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

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

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

и получил:

1

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

    
задан User1 03.12.2010 в 18:30
источник

7 ответов

31

Эквивалентом x? является \(x\|\) .

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

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

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

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

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

    
ответ дан Laurence Gonsalves 03.12.2010 в 18:34
  • Отличный ответ. Я на самом деле не использовал его, потому что в моем конкретном случае был ярлык. Тем не менее, спасибо, что упомянул о жадности моего. * .. что на самом деле все исправлено. –  User1 03.12.2010 в 20:44
  • FWIW, в OS X (и, возможно, в других BSD) флаг -E. -r не существует. –  Bo Jeanes 17.03.2013 в 07:10
  • В BSD sed (проверено на FreeBSD 11), ни | или же ? похоже, работают, даже когда убежали. Но sed -E принимает либо - так и x? и (x |). –  mwfearnley 24.08.2018 в 10:03
6

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

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

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

В словах из документации GNU sed (просмотр с помощью команды «info sed» в Linux):

The only difference between basic and extended regular expressions is in the behavior of a few characters: '?', '+', parentheses, and braces ('{}'). While basic regular expressions require these to be escaped if you want them to behave as special characters, when using extended regular expressions you must escape them if you want them to match a literal character.

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

-r --regexp-extended

Use extended regular expressions rather than basic regular expressions. Extended regexps are those that 'egrep' accepts; they can be clearer because they usually have less backslashes, but are a GNU extension and hence scripts that use them are not portable.

Обновление

Более новые версии GNU sed теперь говорят следующее:

-E -r --regexp-extended

Use extended regular expressions rather than basic regular expressions. Extended regexps are those that 'egrep' accepts; they can be clearer because they usually have fewer backslashes. Historically this was a GNU extension, but the '-E' extension has since been added to the POSIX standard (http://austingroupbugs.net/view.php?id=528), so use '-E' for portability. GNU sed has accepted '-E' as an undocumented option for years, and *BSD seds have accepted '-E' for years as well, but scripts that use '-E' might not port to other older systems.

Итак, если вам нужно сохранить совместимость с древним GNU sed, придерживайтесь -r . Но если вы предпочитаете лучшую кроссплатформенную переносимость в более современных системах (например, поддержка Linux + Mac), используйте -E (но учтите, что между GNU sed и BSD sed все еще есть некоторые причуды и различия, поэтому вам придется убедитесь, что ваши сценарии переносимы в любом случае).

    
ответ дан amichair 15.03.2013 в 00:57
1
echo 'file_1.gz'|sed -n 's/.*_\(.*\)\?\(\.gz\)//p'

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

    
ответ дан Andrew Sledge 03.12.2010 в 18:34
  • Но echo 'file_1' | sed -n 's /.*_ \ (. * \) \? \ (\. gz \) / \ 1 / p' не работает. Была ли опечатка? –  User1 03.12.2010 в 20:34
  • Работал для меня на моей коробке –  Andrew Sledge 06.12.2010 в 17:45
0

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

realname () {
  local n=${$1##*/}
  local rn="${n%.*}"
  sed 's/^.*\_//g' ${$rn:-$n}
}
    
ответ дан Wesley Rice 03.12.2010 в 18:44
0

Вы должны использовать 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

    
ответ дан SiegeX 03.12.2010 в 18:36
0

Часть решения состоит в том, чтобы избежать знака вопроса или использовать опцию -r .

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

или

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

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

file_1.gz
file_12.txt
file_123

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

1
12
123
    
ответ дан Dennis Williamson 03.12.2010 в 19:30
0

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

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

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

    
ответ дан User1 03.12.2010 в 20:42