Сопоставление с образцом в путевых именах в bash

10

Я хочу действовать в списке подкаталогов в каталоге. Рассматривать:

for x in x86-headers/*/C/populate.sh; do echo $x; done

Это дает

x86-headers/elf/C/populate.sh
x86-headers/gl/C/populate.sh
x86-headers/gmp/C/populate.sh
x86-headers/gnome2/C/populate.sh
x86-headers/gtk2/C/populate.sh
x86-headers/jni/C/populate.sh
x86-headers/libc/C/populate.sh

Но я хочу значения, соответствует второй части пути, elf, glи т.д. Я знаю , как сдирать ведущий x86-headers.

for x in x86-headers/*/C/populate.sh; do i=${x##x86-headers/}; echo $i; done

который дает

elf/C/populate.sh
gl/C/populate.sh
gmp/C/populate.sh
gnome2/C/populate.sh
gtk2/C/populate.sh
jni/C/populate.sh
libc/C/populate.sh

Я также знаю, как избавиться от более поздних сроков в пути. Т.е. спускаясь на один уровень:

cd x86-headers
for x in */C/populate.sh; do i=${x%%/*}; echo $i; done

дает

elf
gl
gmp
gnome2
gtk2
jni
libc

Но попытка объединить это не работает. Т.е.

for x in x86-headers/*/C/populate.sh; do i=${${x##x86-headers}%%/*}; echo $i; done

дает

bash: ${${x##x86-headers}%%/*}: bad substitution

Без сомнения, это неправильный синтаксис, но я не знаю правильный синтаксис. Конечно, могут быть лучшие способы сделать это. Если бы я использовал Python, я бы использовал split on, /чтобы разбить каждый путь на список, а затем выбрать второй элемент, но я не знаю, как это сделать в bash.

РЕДАКТИРОВАТЬ: Спасибо за ответы. Я должен был также спросить, возможно ли сделать это переносимо, и если да, то как?

Фахим Митха
источник

Ответы:

9

Вы не можете объединить их bash(или POSIXly), вы должны сделать это в два этапа.

i=${x#*/}; i=${i%%/*}

Это переносимо на любую оболочку POSIX. Если вам нужна переносимость в оболочку Bourne (но зачем вам тогда указывать свой вопрос / bash ?), А также в случае, если вы портируете на Solaris и вынуждены использовать его /bin/shвместо стандартного shили переносить на 20-летние системы) , вы можете использовать этот подход (который будет работать также с оболочками POSIX):

IFS=/; set -f
set x $x
i=$3

(выше один из очень редких случаев, когда имеет смысл оставлять переменную без кавычек).

Просто для записи, с Zsh:

print -rl -- x86-headers/*/C/populate.sh(:h:h:t)

( т Айыл о ч EAD в час EAD файла).

Или для вашего pythonподхода:

x=elf/C/populate.sh
i=${${(s:/:)x}[2]}
Стефан Шазелас
источник
Точка с запятой i=${x#*/}; i=${i%%/*}необязательна, верно?
Дмитрий Радулов
3
@DimitreRadoulov Это необязательно, но некоторые оболочки, такие как некоторые старые версии ash/,dash будут выполнять назначения справа налево (именно так велась оболочка Борна) и вызывать проблемы в этом случае.
Стефан Шазелас
7

Расширение параметров не позволяет вам вкладывать выражения, поэтому вы вынуждены сделать это в два шага с присваиванием:

i=${x##x86-headers/}
i=${i%%/*}

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

IFS=/
set -f # Disable globbing in case unquoted $x has globs in it
i=( $x )
set +f
echo "${i[1]}"

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

# For clarity, assume no names have linebreaks in them
find . -name populate.sh | cut -d / -f 3
Кодзиро
источник
Это не относится ко всем оболочкам, хотя и zshпозволяет вкладывать.
Стефан Шазелас
3
@ StephaneChazelas достаточно справедливо, но этот вопрос помечен bash.
Кодзиро
2
regex="x86-headers/([^/]*)/.*"
for f in x86-headers/*/C/populate.sh; do [[ $f =~ $regex ]] && echo "${BASH_REMATCH[1]}"; done
elf
gl
gmp
gnome2
gtk2
jni
libc
watael
источник
2

Будьте очень осторожны, используя следующую конструкцию:

# What happen if no files match ?
for x in x86-headers/*/C/populate.sh; do
  x=${x##x86-headers/}
  x=${x%%/*}
  echo $x
done

Как вы могли бы в конечном итоге делать echo *. В этом случае это только смешно, но это может быть гораздо хуже, если вы root, ваш CWDis, /и вы хотите удалить некоторые подкаталоги /tmpвместо того, чтобы повторять их!

Чтобы исправить это в bash, вы можете сделать:

shopt -s nullglob
for x in x86-headers/*/C/populate.sh; do
  x=${x##x86-headers/}
  x=${x%%/*}
  echo $x
done
shopt -u nullglob

Обратите внимание shopt -u nullglobна в конце, чтобы восстановить поведение bash по умолчанию после цикла.

Более портативное решение может быть:

for x in x86-headers/*/C/populate.sh; do
  [ -e $x ] || break
  x=${x##x86-headers/}
  x=${x%%/*}
  echo $x
done

( Подстановки ##и %%параметров действительны в ksh88 ).

Перейдите по этой ссылке для более полного обсуждения nullglob .

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

for x in x86-headers/*/C/populate.sh; do
  [ -e "$x" ] || break
  x="${x##x86-headers/}"
  x="${x%%/*}"
  echo "$x"
done
jfg956
источник
0

Для переноса сопоставления с образцом в путевых именах вы можете установить для IFSпеременной оболочки значение, /а затем использовать расширение параметров оболочки.

Выполнение всего этого в подоболочке помогает сохранить среду родительской оболочки неизменной!

# cf. "The real Bourne shell problem",
# http://utcc.utoronto.ca/~cks/space/blog/programming/BourneShellLists

paths='
x86-headers/elf/C/populate.sh
x86-headers/gl/C/populate.sh
x86-headers/gmp/C/populate.sh
x86-headers/gnome2/C/populate.sh
x86-headers/gtk2/C/populate.sh
x86-headers/jni/C/populate.sh
x86-headers/libc/C/populate.sh
'

IFS='
'

# version 1
for x in $paths; do 
   ( IFS='/'; set -- ${x}; echo "$2" ); 
done


# version 2
set -- ${paths}
for x in $@; do 
   ( IFS='/'; set -- ${x}; echo "$2" ); 
done
карло
источник