Khớp mẫu trên tên đường dẫn trong bash


10

Tôi muốn hành động trên một danh sách các thư mục con trong một thư mục. Xem xét:

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

Điều này mang lại

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

Nhưng tôi muốn các giá trị tương ứng với phần thứ hai của đường dẫn, elf , gl, vv Tôi biết làm thế nào để lột hàng đầu x86-headers.

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

cái nào cho

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

Tôi cũng biết làm thế nào để loại bỏ các điều khoản sau này trong đường dẫn. Tức là đi xuống một cấp:

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

cho

elf
gl
gmp
gnome2
gtk2
jni
libc

Nhưng, cố gắng kết hợp những thứ này không hoạt động. I E

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

cho

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

Không nghi ngờ gì đây là cú pháp không chính xác, nhưng tôi không biết cú pháp chính xác. Tất nhiên có thể có những cách tốt hơn để làm điều này. Nếu tôi đang sử dụng Python, tôi sẽ sử dụng split on /để chia từng đường dẫn thành một danh sách, sau đó chọn phần tử thứ hai, nhưng tôi không biết làm thế nào để làm điều đó trong bash.

EDIT: Cảm ơn câu trả lời. Tôi cũng nên hỏi, liệu có thể làm điều này một cách hợp lý, và nếu vậy, làm thế nào?

Câu trả lời:


9

Bạn không thể kết hợp chúng với bash(hoặc POSIXly), bạn phải thực hiện theo hai bước.

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

Đó là di động cho bất kỳ vỏ POSIX. Nếu bạn cần tính di động đối với trình bao Bourne (nhưng tại sao bạn lại gắn thẻ câu hỏi / bash của mình ?) Trong trường hợp bạn chuyển sang Solaris và buộc phải sử dụng /bin/shthay vì tiêu chuẩn shở đó hoặc chuyển sang hệ thống 20 năm tuổi) , bạn có thể sử dụng phương pháp này (cũng sẽ hoạt động với shell POSIX):

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

(ở trên là một trong những trường hợp rất hiếm khi có ý nghĩa để lại một biến không được trích dẫn).

Chỉ để ghi lại, với zsh:

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

( t ail của h ead của h ead của tập tin).

Hoặc cho pythoncách tiếp cận của bạn :

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

Dấu chấm phẩy trong i=${x#*/}; i=${i%%/*}là tùy chọn, phải không?
Dimitre Radoulov

3
@DimitreRadoulov Tùy chọn nhưng một số shell như một số phiên bản cũ của ash/dash sẽ chạy các bài tập từ phải sang trái (đó là cách vỏ Bourne hoạt động) và gây ra vấn đề trong trường hợp này.
Stéphane Chazelas

7

Mở rộng tham số không cho phép bạn lồng các biểu thức, vì vậy bạn buộc phải thực hiện theo hai bước, với một nhiệm vụ:

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

Bạn có tùy chọn sử dụng mảng ở đây, nhưng tôi nghĩ nó sẽ cồng kềnh hơn PE ở trên:

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

Sau đó, có tùy chọn thứ ba sử dụng các lệnh bên ngoài. Với một danh sách ngắn các tệp, đây thường là tùy chọn kém hiệu quả nhất, nhưng nó sẽ mở rộng tốt nhất khi số lượng tệp tăng lên:

# For clarity, assume no names have linebreaks in them
find . -name populate.sh | cut -d / -f 3

Điều đó không đúng với tất cả các vỏ mặc dù, zshcho phép làm tổ.
Stéphane Chazelas

3
@StephaneChazelas đủ công bằng, nhưng câu hỏi này được gắn thẻ 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

Hãy cẩn thận khi sử dụng cấu trúc dưới đây:

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

Như bạn sẽ có thể làm một echo *. Trong trường hợp này, nó chỉ buồn cười, nhưng nó có thể tồi tệ hơn nhiều nếu bạn là root, CWDlà của bạn /và bạn muốn xóa một số thư mục con của/tmp thay vì lặp lại chúng!

Để khắc phục điều đó trong bash, bạn có thể làm:

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

Lưu ý shopt -u nullglobở cuối để khôi phục hành vi bash mặc định sau vòng lặp.

Một giải pháp di động hơn có thể là:

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

( Thay thế ##%%tham số có giá trị trong ksh88 ).

Theo liên kết này để thảo luận đầy đủ hơn về nullglob .

Lưu ý rằng 3 giải pháp trên không thành công nếu bạn có một thư mục trung gian có khoảng trắng trong tên của nó. Để giải quyết nó, sử dụng dấu ngoặc kép trong thay thế biến:

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

0

Để thực hiện khớp mẫu trên tên đường dẫn, bạn có thể đặt IFSbiến shell thành/ và sau đó sử dụng mở rộng tham số shell.

Làm tất cả điều này trong một lớp con giúp giữ cho môi trường của lớp vỏ mẹ không thay đổi!

# 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
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.