
В скрипте 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'
Но... в вашем случае вам лучше использовать другие инструменты...