Chia chuỗi trên dấu hai chấm trong / bin / sh


9

dashKịch bản của tôi có một tham số ở dạng hostname:port, nghĩa là:

myhost:1234

Trong khi đó cổng là tùy chọn, nghĩa là:

myhost

Tôi cần đọc máy chủ và cổng thành các biến riêng biệt. Trong trường hợp đầu tiên, tôi có thể làm:

HOST=${1%%:*}
PORT=${1##*:}

Nhưng điều đó không hoạt động trong trường hợp thứ hai, khi cổng bị bỏ qua; echo ${1##*:}chỉ cần trả về tên máy chủ, thay vì một chuỗi rỗng.

Ở Bash, tôi có thể làm:

IFS=: read A B <<< asdf:111

Nhưng điều đó không làm việc trong dash.

Tôi có thể chia nhỏ chuỗi trên :trong dấu gạch ngang, mà không viện dẫn chương trình bên ngoài ( awk, tr, vv)?


4
Đảm bảo tách trên dấu hai chấm cuối cùng nếu bạn muốn hỗ trợ IPv6 và không phân chia dấu hai chấm trong dấu ngoặc vuông
Ferrybig

@Ferrybig %%làm cho nó tham lam (trái ngược với %), vì vậy nó thực sự làm điều này, ít nhất là một phần; nó sẽ không hoạt động với ##.
jpaugh

Câu trả lời:


18

Cứ làm đi:

case $1 in
  (*:*) host=${1%:*} port=${1##*:};;
  (*)   host=$1      port=$default_port;;
esac

Bạn có thể muốn thay đổi case $1để case ${1##*[]]}vào tài khoản cho các giá trị của $1giống [::1](một địa chỉ IPv6 mà không cần cổng phần).

Để phân tách, bạn có thể sử dụng toán tử split + global (không mở rộng tham số) như tất cả những gì nó dành cho:

set -o noglob # disable glob part
IFS=:         # split on colon
set -- $1     # split+glob

host=$1 port=${2:-$default_port}

(mặc dù điều đó sẽ không cho phép tên máy chủ chứa dấu hai chấm (như địa chỉ IPv6 ở trên)).

Nhà điều hành phân chia + toàn cầu này gây cản trở và gây ra rất nhiều tác hại trong thời gian còn lại đến nỗi nó dường như chỉ công bằng khi sử dụng bất cứ khi nào cần thiết (mặc dù vậy, tôi đồng ý rằng nó rất cồng kềnh khi sử dụng, đặc biệt là khi POSIX shkhông có hỗ trợ cho phạm vi cục bộ, không phải cho các biến ( $IFSở đây) cũng như cho các tùy chọn ( noglobở đây) (mặc dù ashvà các dẫn xuất giống như dashmột số trong số đó thực hiện (cùng với các triển khai AT & T của ksh, zshbash4.4 trở lên)).

Lưu ý rằng IFS=: read A B <<< "$1"có một vài vấn đề của riêng nó:

  • bạn đã quên -rđiều đó có nghĩa là dấu gạch chéo ngược sẽ trải qua một số xử lý đặc biệt.
  • nó sẽ tách [::1]:443thành [:1]:443thay vì [và chuỗi rỗng (mà bạn cần IFS=: read -r A B rest_ignoredhay [::1]443(mà bạn không thể sử dụng phương pháp đó)
  • nó loại bỏ mọi thứ qua lần xuất hiện đầu tiên của một ký tự dòng mới, do đó, nó không thể được sử dụng với các chuỗi tùy ý (trừ khi bạn sử dụng -d ''trong zshhoặc bashvà dữ liệu không chứa các ký tự NUL, nhưng sau đó lưu ý rằng các herestrings (hoặc heredocs) sẽ thêm một thêm nhân vật dòng mới!)
  • trong zsh(nơi cú pháp xuất phát) và bash, ở đây các chuỗi được triển khai bằng các tệp tạm thời, do đó, nó thường kém hiệu quả hơn so với sử dụng ${x#y}hoặc tách + toán tử toàn cục.

7
Năm 2018, với tư cách là một giải pháp cho năm mới, tất cả chúng ta nên ngừng viết các tập lệnh sẽ phá vỡ bằng IPv6.
Philippos

@Philippos quá muộn hai tuần!
RonJohn

@RonJohn: Quá muộn bởi hai thập kỷ, bằng cách nào đó.
Philippos

6

Chỉ cần loại bỏ :trong một tuyên bố riêng biệt; đồng thời, xóa $ host khỏi đầu vào để lấy cổng:

host=${1%:*}
port=${1#"$host"}
port=${port#:}


1

Chuỗi ở đây chỉ là một phím tắt cú pháp cho một tài liệu một dòng ở đây.

$ set myhost:1234
$ IFS=: read A B <<EOF
> $1
> EOF
$ echo "$A"
myhost
$ echo "B"
1234
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.