bash làm thế nào để loại bỏ các tùy chọn khỏi các tham số sau khi xử lý


9

Tôi nhớ đã từng thấy ở đâu đó một bashtập lệnh sử dụng casevà duyệt shiftqua danh sách các tham số vị trí, cờ phân tích và các tùy chọn với các đối số khi nó gặp chúng và xóa chúng sau khi phân tích cú pháp để chỉ các đối số trần, phần còn lại được xử lý bởi phần còn lại của kịch bản.

Ví dụ, khi phân tích cú pháp dòng lệnh cp -R file1 -t /mybackup file2 -f, trước tiên, nó sẽ đi qua các tham số, nhận ra rằng người dùng đã yêu cầu xuống thư mục bằng cách -R, chỉ định mục tiêu bằng cách -t /mybackupvà buộc sao chép -fvà xóa chúng khỏi danh sách tham số, để lại chương trình để xử lýfile1 file2 như các đối số còn lại.

Nhưng dường như tôi không thể nhớ / tìm ra bất kỳ kịch bản nào tôi thấy bất cứ khi nào. Tôi chỉ muốn có thể làm điều đó. Tôi đã tìm kiếm trên các trang web khác nhau và thêm một danh sách các trang có liên quan mà tôi đã xem xét.

Một câu hỏi trên trang web này được hỏi cụ thể về "tùy chọn độc lập theo thứ tự" nhưng cả câu trả lời duy nhất và câu trả lời của câu hỏi đã bị lừa để không xem xét các trường hợp như ở trên, nơi các tùy chọn được trộn lẫn với các đối số bình thường, mà tôi cho là lý do để người đề cập cụ thể đến độc lập trật tự lựa chọn .

Vì tính bashnăng tích hợp sẵn getoptsdường như dừng lại ở đối số không phải tùy chọn đầu tiên, nên dường như nó không đủ để giải quyết. Đây là lý do tại sao trang của Wooledge BashFAQ (xem bên dưới) giải thích cách sắp xếp lại các đối số. Nhưng tôi muốn tránh tạo nhiều mảng trong trường hợp danh sách đối số khá dài.

shiftkhông hỗ trợ bật các đối số riêng lẻ ra giữa danh sách tham số, tôi không chắc đâu là cách đơn giản để thực hiện những gì tôi đang hỏi.

Tôi muốn nghe nếu có ai có bất kỳ giải pháp nào để loại bỏ các đối số từ giữa danh sách tham số mà không tạo ra một mảng hoàn toàn mới.

Các trang mà tôi đã xem:


1
Khi tôi cần một cái gì đó phức tạp, tôi chuyển từ bash sang Perl.
choroba

Câu trả lời:


9

POSIXly, việc phân tích cú pháp cho các tùy chọn nên dừng tại --hoặc tại đối số không phải tùy chọn đầu tiên (hoặc không đối số tùy chọn) tùy theo điều kiện nào đến trước. Vậy trong

cp -R file1 -t /mybackup file2 -f

mà tại file1, vì vậy cpđệ quy nên sao chép tất cả các file1, -t, /mybackupfile2vào -fthư mục.

getopt(3)Tuy nhiên GNU (mà GNU cpsử dụng để phân tích các tùy chọn (và ở đây bạn đang sử dụng GNU cpvì bạn đang sử dụng -ttùy chọn dành riêng cho GNU )), trừ khi $POSIXLY_CORRECTbiến môi trường được đặt, chấp nhận các tùy chọn sau các đối số. Vì vậy, nó thực sự tương đương với phân tích cú pháp kiểu tùy chọn POSIX:

cp -R -t /mybackup -f -- file1 file2

Các getoptsvỏ tích hợp, ngay cả trong vỏ GNU ( bash) chỉ xử lý các kiểu POSIX. Nó cũng không hỗ trợ các tùy chọn dài hoặc tùy chọn với các đối số tùy chọn.

Nếu bạn muốn phân tích các tùy chọn giống như GNU cp, bạn sẽ cần sử dụng getopt(3)API GNU . Vì thế, nếu trên Linux, bạn có thể sử dụng getopttiện ích nâng cao từ util-linux( phiên bản nâng cao của getoptlệnh này cũng đã được chuyển sang một số Unice khác như FreeBSD ).

Điều đó getoptsẽ sắp xếp lại các tùy chọn theo cách chính tắc cho phép bạn phân tích cú pháp đơn giản bằng một while/casevòng lặp.

$ getopt -n "$0" -o t:Rf -- -Rf file1 -t/mybackup file2
 -R -f -t '/mybackup' -- 'file1' 'file2'

Bạn thường sử dụng nó như:

parsed_options=$(
  getopt -n "$0" -o t:Rf -- "$@"
) || exit
eval "set -- $parsed_options"
while [ "$#" -gt 0 ]; do
  case $1 in
    (-[Rf]) shift;;
    (-t) shift 2;;
    (--) shift; break;;
    (*) exit 1 # should never be reached.
  esac
done
echo "Now, the arguments are $*"

Cũng lưu ý rằng điều đó getoptsẽ phân tích các tùy chọn giống như GNU cp. Cụ thể, nó hỗ trợ các tùy chọn dài (và nhập chúng viết tắt) và tôn vinh các $POSIXLY_CORRECTbiến môi trường (khi thiết lập sẽ vô hiệu hóa hỗ trợ cho các tùy chọn sau các đối số) giống như cách GNU cpthực hiện.

Lưu ý rằng việc sử dụng gdb và in các đối số getopt_long()nhận được có thể giúp xây dựng các tham số thành getopt(1):

(gdb) bt
#0  getopt_long (argc=2, argv=0x7fffffffdae8, options=0x4171a6 "abdfHilLnprst:uvxPRS:T", long_options=0x417c20, opt_index=0x0) at getopt1.c:64
(gdb) set print pretty on
(gdb) p *long_options@40
$10 = {{
    name = 0x4171fb "archive",
    has_arg = 0,
    flag = 0x0,
    val = 97
  }, {
    name = 0x417203 "attributes-only",
[...]

Sau đó, bạn có thể sử dụng getoptnhư:

getopt -n cp -o abdfHilLnprst:uvxPRS:T -l archive... -- "$@"

Hãy nhớ rằng cpdanh sách các tùy chọn được hỗ trợ của GNU có thể thay đổi từ phiên bản này sang phiên bản tiếp theo và điều đó getoptsẽ không thể kiểm tra nếu bạn chuyển một giá trị pháp lý cho --sparsetùy chọn đó.


@all: cảm ơn bạn đã trả lời @Stephane: có lý do gì bạn đang sử dụng while [ "$#" -gt 0 ]io while (($#))không? Có phải chỉ để tránh một bashism?
jamadagni

Vâng, mặc dù (($#))là nhiều hơn một kshism .
Stéphane Chazelas

1

Vì vậy, mỗi khi getoptsxử lý một đối số, nó không mong đợi nó đặt biến shell $OPTINDthành số tiếp theo trong danh sách đối số cần xử lý và trả về khác 0. Nếu $OPTINDđược đặt thành giá trị 1, getoptsđược chỉ định POSIX để chấp nhận danh sách đối số mới. Vì vậy, đây chỉ là đồng hồ getoptstrả về, tiết kiệm gia tăng số lần truy cập cộng với $OPTINDbất kỳ lần trả lại không thành công nào, loại bỏ các đối số không thành công và đặt lại $OPTINDmỗi lần thử thất bại. Bạn có thể sử dụng nó như thế opts "$@"- mặc dù bạn muốn tùy chỉnh casevòng lặp hoặc nếu không thì lưu nó vào một biến và thay đổi phần đó thành eval $case.

opts() while getopts :Rt:f opt || {
             until command shift "$OPTIND" || return 0
                   args=$args' "${'"$((a=${a:-0}+$OPTIND))"'}"'
                   [ -n "${1#"${1#-}"}" ]
             do OPTIND=1; done 2>/dev/null; continue
};     do case "$opt"  in
             R) Rflag=1      ;;
             t) tflag=1      ;
                targ=$OPTARG ;;
             f) fflag=1      ;;
       esac; done

Trong khi chạy, nó đặt $argsthành mọi đối số getoptskhông xử lý ... vì vậy ...

set -- -R file1 -t /mybackup file2 -f
args= opts "$@"; eval "set -- $args"
printf %s\\n "$args"
printf %s\\n "$@"         
printf %s\\n "$Rflag" "$tflag" "$fflag" "$targ"

ĐẦU RA

 "${2}" "${5}"
file1
file2
1
1
1
/mybackup

Này hoạt động trong bash, dash, zsh,ksh93 , mksh... tốt, tôi bỏ cố gắng vào thời điểm đó. Trong mỗi vỏ nó cũng có $[Rtf]flag$targ. Vấn đề là tất cả các số cho các đối số getoptskhông muốn xử lý vẫn còn.

Thay đổi phong cách tùy chọn cũng không có sự khác biệt. Nó hoạt động như thế nào -Rf -t/mybackuphoặc -R -f -t /mybackup. Nó hoạt động ở giữa danh sách, ở cuối danh sách hoặc ở đầu danh sách ...

Tuy nhiên, cách tốt nhất là chỉ cần dán một phần --cuối của các tùy chọn vào danh sách đối số của bạn và sau đó thực hiện shift "$(($OPTIND-1))"ở cuối phầngetopts quá trình xử lý. Bằng cách đó, bạn loại bỏ tất cả các tham số đã xử lý giữ phần cuối của danh sách đối số.

Một điều tôi muốn làm là dịch các tùy chọn dài thành ngắn - và tôi làm điều đó theo cách rất giống nhau, đó là lý do tại sao câu trả lời này đến dễ dàng - trước khi tôi chạy getopts.

i=0
until [ "$((i=$i+1))" -gt "$#" ]
do case "$1"                   in
--Recursive) set -- "$@" "-R"  ;;
--file)      set -- "$@" "-f"  ;;
--target)    set -- "$@" "-t"  ;;
*)           set -- "$@" "$1"  ;;
esac; shift; done

Chuyển đổi tùy chọn dài của bạn cũng sẽ chuyển đổi các tùy chọn không (như cp -t --file foohoặc cp -- --file foo) và sẽ không đối phó với các tùy chọn được viết tắt ( --fi...) hoặc theo --target=/destcú pháp.
Stéphane Chazelas

@ StéphaneChazelas - điều chuyển đổi dài chỉ là một ví dụ - rõ ràng nó đã không được xây dựng rất tốt. Tôi đã thay thế getoptsđiều đó bằng một chức năng đơn giản hơn nhiều.
mikeerv

@ StéphaneChazelas - vui lòng xem lại. Trong khi bình luận tùy chọn dài vẫn hợp lý, thì đầu tiên - và downvote đi kèm của nó - không còn như tôi nghĩ nữa. Ngoài ra, tôi nghĩ rằng opts()có một số vấn đề đối với câu trả lời của riêng bạn khi opts()hoạt động trong một vòng lặp - chạm vào từng đối số nhưng một lần - và thực sự đáng tin cậy (gần như tôi có thể nói) , một cách rõ ràng và không có một nhánh con nào.
mikeerv

đặt lại OPTIND là những gì tôi gọi là bắt đầu một vòng lặp getopts khác. Trong thực tế, đó là phân tích một vài dòng lệnh / bộ tùy chọn ( -R, -t /mybackup, -f). Bây giờ tôi sẽ tiếp tục bỏ phiếu vì nó vẫn bị xáo trộn, bạn đang sử dụng $achưa được cấp phép và args= opts...có khả năng argskhông đặt (hoặc đặt thành giá trị trước đó) sau khi optstrả về nhiều vỏ (bao gồm cả bash).
Stéphane Chazelas

@ StéphaneChazelas - bao gồm bash- Tôi ghét điều đó. Theo tôi, nếu hàm là hàm shell hiện tại nên được giữ lại. Tôi đang giải quyết những điều này - vì chức năng hiện tại, với nhiều thử nghiệm hơn, có thể được thực hiện mạnh mẽ để xử lý tất cả các trường hợp và thậm chí để gắn cờ đối số với tùy chọn trước đó, nhưng tôi không đồng ý rằng nó bị che khuất . Như đã viết, nó là phương tiện đơn giảntrực tiếp nhất để hoàn thành nhiệm vụ mà tôi có thể nghĩ ra. testing sau đó shifting có ý nghĩa rất nhỏ khi trong mọi shifttrường hợp thất bại bạn nên return. Nó không có ý định làm khác.
mikeerv
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.