Нежадное сопоставление в sed

Нежадное сопоставление в sed

В скрипте bash у меня есть следующая переменная:

file_name='this_is_the_hart_part.csv'

С использованием

var2=$(echo $file_name | sed -e 's/_{2}\(.*\)_{3}/\1/')

Я хочу извлечь подстроку «the» (между подчеркиваниями номер 2 и 3 в переменной $file_name).

Но я получаю $var2, равный $file_name. Как мне изменить команду sed?

решение1

Типы регулярных выражений, поддерживаемые , sedне допускают нежадного сопоставления с *.

Вы хотите получить 3-е _поле с разделителем. Это проще всего сделать с помощью cut:

cut -d '_' -f 3

Или, с awk:

awk -F '_' '{ print $3 }'

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

str=${file_name#*_}
str=${str#*_}
str=${str%%_*}

"$str"будет слово theв конце. Использование этого последнего варианта, вероятно, будет самым быстрым и надежным способом из этих трех.

Подстановка переменных ${variable#*_}приведет к строке, в которой $variableначальный бит до первого подчеркивания включительно будет удален. Удалится ${variable%%_*}все от первого подчеркивания до конца $variable. Это стандартные подстановки переменных.

Преимущество использования подстановки переменных в имени файла заключается в том, что она справится с именами файлов, содержащими символы новой строки, чего не сделали бы awkни sedили cut. В общем случае не используйте инструменты редактирования текста, ориентированные на строки, в именах файлов.

Кроме того, вы используете echo $file_name. Поскольку $file_nameне заключено в кавычки, оно подвергнется разрезанию слов (по каждому символу, который также является частью $IFS; пробел, табуляция и новая строка по умолчанию), и сгенерированные слова, если они содержат символы подстановки имени файла, будут сопоставлены оболочкой с именами файлов в текущем каталоге. А обратные косые черты в имени файла также могут исчезнуть или иметь нежелательные эффекты (даже если вы заключаете расширение в кавычки). Оболочка kshтакже выполнит расширение фигурных скобок для значения , $file_nameкогда оно не заключено в кавычки.

решение2

Первая заметка, которая sedявляетсятекстутилита, которая по умолчанию работает по одной строке за раз, в то время как имена файлов могут содержать любые символы (включая новую строку) и даже не символы (могут быть не-текст).

Также,оставление переменной без кавычек имеет особое значение, вы почти никогда не захотите этого делать, это такжепотенциально очень опасно.

Также,вы не можете использовать echoдля вывода произвольных данных, printfвместо этого используйте.

Кроме того, синтаксис назначения переменных в оболочках типа Bourne выглядит так: var=value, а не $var=value.

Вы можете загрузить весь вывод echo(или, лучше, printf) в sedпространство шаблонов с помощью:

printf '%s\n' "$filename" | sed -e :1 -e '$!{N;b1' -e '}'

Затем вы можете добавить код для извлечения части между второй и третьей _:

var2=$(
  printf '%s\n' "$filename" |
   sed -ne :1 -e '$!{N;b1' -e '}' -e 's/^\([^_]*_\)\{2\}\([^_]*\)_.*/\2/p'
)

Нежадная часть решается с помощью [^_]*(последовательности не- _символов), которая, вопреки .*гарантиям, не будет соответствовать прошлым _границам (хотя во многих реализациях она все равно будет подавляться не-символами).

В этом случае вместо этого можно использовать операторы расширения параметров оболочки:

case $filename in
  (*_*_*_*) var2=${filename#*_*_}; var2=${var2%%_*};;
  (*)       var2=;;
esac

Это сработает лучше, если имя файла не является текстовым или если часть, которую вы хотите извлечь, заканчивается символом новой строки (и также будет более эффективно).

Некоторые оболочки, такие как zshили , ksh93имеют более продвинутые операторы:

  • zsh:

    разделяем _и получаем третье поле:

    var2=${"${(@s:_:)filename}"[3]}
    

    Используя ${var/pattern/replacement}обратные ссылки и (в этом случае сначала необходимо убедиться, что переменная содержит не менее 3 символов подчеркивания, иначе подстановки не будет).

    set -o extendedglob
    var2=${filename/(#b)*_*_(*)_*/$match[1]}
    
  • ksh93:

    var2=${filename/*_*_@(*)_*/\1}
    

решение3

@Kusalananda прав, это sedнеправильный инструмент, и вы не можете выполнить нежадное сопоставление. Но вы можете использовать обходной путь для нежадного сопоставления: [^_]*будет соответствовать любому символу, который не является_

Так что в вашем случае вы могли бы сделать что-то вроде этого:

printf '%s\n' "$file_name" | sed -e 's/^[^_]*_[^_]*_\([^_]*\).*$/\1/g'

Но... в вашем случае вам лучше использовать другие инструменты...

Связанный контент