Зіставлення шаблонів у назвах шляхів у 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 на, /щоб перебити кожен шлях у список, а потім вибрати другий елемент, але я не знаю, як це зробити в bash.

EDIT: Дякую за відповіді. Я також повинен запитати, чи можна це робити портативно, і якщо так, то як?

Відповіді:


9

Ви не можете комбінувати їх з bash(або POSIXly), це потрібно зробити в два етапи.

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

Це портативно для будь-якої оболонки POSIX. Якщо вам потрібна портативність до оболонки Борна (але навіщо ви тоді тегувати своє запитання / баш ?), А також у випадку, якщо ви переносите на 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)

( t ail of h ead of h ead файла).

Або для вашого pythonпідходу:

x=elf/C/populate.sh
i=${${(s:/:)x}[2]}

Точка з крапкою з комою i=${x#*/}; i=${i%%/*}необов’язково, правда?
Димитрій Радулов

3
@DimitreRadoulov Це необов’язково, але деякі оболонки, як-то старі версії ash/dash , виконували б завдання справа наліво (саме так поводилася оболонка Борна) і спричинили б проблему в цьому випадку.
Stéphane Chazelas

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дозволяє гніздування.
Stéphane Chazelas

3
@StephaneChazelas досить справедливо, але це питання позначене тегами bash.
kojiro

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

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, ваш CWDє /і ви хочете видалити деякі підкаталоги, /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 ).

Перейдіть за цим посиланням для більш повного обговорення нульглобу .

Зауважте, що 3 рішення, наведені вище, не спрацьовують, якщо у вас є посередницький каталог із пробілом у його назві. Для її вирішення використовуйте подвійні лапки в підстановці змінної:

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

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
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.