Làm thế nào để tách một chuỗi phân tách thành một mảng trong awk?


169

Làm thế nào để tách chuỗi khi nó chứa các ký hiệu ống |trong đó. Tôi muốn tách chúng thành mảng.

Tôi đã thử

echo "12:23:11" | awk '{split($0,a,":"); print a[3] a[2] a[1]}'

Mà hoạt động tốt. Nếu chuỗi của tôi giống như vậy "12|23|11"thì làm cách nào để chia chúng thành một mảng?


3
Lưu ý rằng đầu ra của bạn đang nối các phần tử mảng, không có dấu phân cách. Thay vào đó, nếu bạn muốn tách chúng ra OFS, hãy đặt dấu phẩy ở giữa chúng, làm cho printchúng được xem như là các đối số riêng biệt.
dubiousjim

Hoặc bạn có thể sử dụng sed:echo "12:23:11" | sed "s/.*://"
lấm lem

@slushy: lệnh của bạn không phải là tất cả những gì người hỏi cần. lệnh của bạn ( echo "12:23:11" | sed "s/.*://") xóa mọi thứ cho đến khi (và bao gồm) ":" cuối cùng, chỉ giữ lại "11" ... nó hoạt động để lấy số cuối cùng, nhưng sẽ cần phải sửa đổi (theo cách khó đọc) để có được số thứ 2, v.v ... awk (và awk's split) thanh lịch và dễ đọc hơn nhiều.
Olivier Dulac

nếu bạn cần tách trên một ký tự bạn có thể sử dụngcut
ccpizza

Câu trả lời:


274

Bạn đã thử chưa:

echo "12|23|11" | awk '{split($0,a,"|"); print a[3],a[2],a[1]}'

2
@Mohamed Saligh, nếu bạn đang sử dụng Solaris, bạn cần sử dụng / usr / xpg4 / bin / awk , với độ dài chuỗi.
Dimitre Radoulov

5
"Không làm việc cho tôi". đặc biệt là với dấu hai chấm giữa các giá trị được lặp lại và phân tách được thiết lập để phân chia trên '|' ??? Typo? Chúc mọi người may mắn.
shellter

1
Tốt hơn với một số giải thích cú pháp.
Alston

2
Điều này sẽ không hoạt động trong GNU awk, bởi vì đối số thứ ba splitlà biểu thức chính quy và |là ký hiệu đặc biệt, cần phải được thoát. Sử dụngsplit($0, a, "\|")
WhiteWind

1
@WhiteWind: một cách khác để "đảm bảo" |được xem là char và không phải là biểu tượng đặc biệt là đặt nó ở giữa []: tức là split($0, a, "[|]") # Tôi thích điều này tốt hơn '\ |', trong một số trường hợp, đặc biệt là một số biến thể của regrec ( perl vs grep vs .. những người khác?) có thể có "|" xen kẽ theo nghĩa đen và "\ |" được xem như là dấu phân cách regex, thay vì ngược lại ... ymmv
Olivier Dulac

119

Để phân tách một chuỗi thành một mảng trong awkchúng ta sử dụng hàm split():

 awk '{split($0, a, ":")}'
 #           ^^  ^  ^^^
 #            |  |   |
 #       string  |   delimiter
 #               |
 #               array to store the pieces

Nếu không có dấu phân cách nào được đưa ra, nó sẽ sử dụng FS, mặc định là khoảng trắng:

$ awk '{split($0, a); print a[2]}' <<< "a:b c:d e"
c:d

Chúng ta có thể đưa ra một dấu phân cách, ví dụ ::

$ awk '{split($0, a, ":"); print a[2]}' <<< "a:b c:d e"
b c

Điều này tương đương với việc thiết lập nó thông qua FS:

$ awk -F: '{split($0, a); print a[1]}' <<< "a:b c:d e"
b c

Trong gawk, bạn cũng có thể cung cấp dấu phân cách dưới dạng regrec:

$ awk '{split($0, a, ":*"); print a[2]}' <<< "a:::b c::d e" #note multiple :
b c

Và thậm chí xem những gì dấu phân cách trên mỗi bước bằng cách sử dụng tham số thứ tư của nó:

$ awk '{split($0, a, ":*", sep); print a[2]; print sep[1]}' <<< "a:::b c::d e"
b c
:::

Hãy trích dẫn trang man của GNU awk :

tách (chuỗi, mảng [, fieldsep [, seps]])

Chia chuỗi thành các phần được phân tách bằng fieldsep và lưu trữ các phần trong mảng và chuỗi phân cách trong mảng seps . Phần đầu tiên được lưu trữ array[1], phần thứ hai array[2]và vv. Giá trị chuỗi của đối số thứ ba, fieldsep , là một biểu thức chính quy mô tả nơi phân tách chuỗi (nhiều như FS có thể là biểu thức chính quy mô tả nơi phân chia các bản ghi đầu vào). Nếu trường bị bỏ qua, giá trị của FS được sử dụng. split()trả về số lượng phần tử được tạo. seps là một gawkphần mở rộng, với seps[i]chuỗi phân cách giữaarray[i]array[i+1]. Nếu fieldsep là một không gian duy nhất, thì bất kỳ khoảng trắng hàng đầu nào cũng đi vào seps[0]và bất kỳ khoảng trắng theo sau nào đi vào seps[n], trong đó n là giá trị trả về của split()(tức là số phần tử trong mảng).


chỉ cần đề cập đến việc bạn đang sử dụng gnu awk, không phải awk thông thường (không lưu trữ dấu phân cách trong seps [] và có các hạn chế khác)
Olivier Dulac

17

Xin hãy cụ thể hơn! Bạn có ý nghĩa gì bởi "nó không hoạt động"? Đăng đầu ra chính xác (hoặc thông báo lỗi), phiên bản hệ điều hành và awk của bạn:

% awk -F\| '{
  for (i = 0; ++i <= NF;)
    print i, $i
  }' <<<'12|23|11'
1 12
2 23
3 11

Hoặc, sử dụng chia:

% awk '{
  n = split($0, t, "|")
  for (i = 0; ++i <= n;)
    print i, t[i]
  }' <<<'12|23|11'
1 12
2 23
3 11

Chỉnh sửa: trên Solaris, bạn sẽ cần sử dụng POSIX awk ( / usr / xpg4 / bin / awk ) để xử lý 4000 trường chính xác.


for(i = 0hay for(i = 1?
PiotrNycz

i = 0, vì tôi sử dụng ++ i sau (không phải i ++).
Dimitre Radoulov

3
Ok - tôi đã không nhận thấy điều này. Tôi tin tưởng mạnh mẽ hơn sẽ dễ đọc hơn for (i = 1; i <= n; ++i)...
PiotrNycz

5

Tôi không thích echo "..." | awk ...giải pháp này vì nó gọi các cuộc gọi hệ thống forkvà không cần thiết exec.

Tôi thích giải pháp của Dimitre với một chút thay đổi

awk -F\| '{print $3 $2 $1}' <<<'12|23|11'

Hoặc phiên bản ngắn hơn một chút:

awk -F\| '$0=$3 $2 $1' <<<'12|23|11'

Trong trường hợp này, bản ghi đầu ra ghép lại với nhau, đó là một điều kiện thực, do đó nó được in.

Trong trường hợp cụ thể này stdin chuyển hướng có thể được thực hiện bằng cách đặt biến nội bộ:

awk -v T='12|23|11' 'BEGIN{split(T,a,"|");print a[3] a[2] a[1]}'

Tôi đã sử dụng khá lâu, nhưng trong điều này có thể được quản lý bằng thao tác chuỗi nội bộ. Trong trường hợp đầu tiên, chuỗi gốc được phân tách bằng terminator nội bộ. Trong trường hợp thứ hai, giả định rằng chuỗi luôn chứa các cặp chữ số được phân tách bằng dấu phân cách một ký tự.

T='12|23|11';echo -n ${T##*|};T=${T%|*};echo ${T#*|}${T%|*}
T='12|23|11';echo ${T:6}${T:3:2}${T:0:2}

Kết quả trong mọi trường hợp là

112312

Tôi nghĩ rằng kết quả cuối cùng được cho là các tham chiếu biến mảng awk, bất kể ví dụ đầu ra in được đưa ra. Nhưng bạn đã bỏ lỡ một trường hợp bash thực sự dễ dàng để cung cấp kết quả cuối cùng của bạn. T = '12: 23: 11 '; echo $ {T //:}
Daniel Liston

@DanielListon Bạn nói đúng! Cảm ơn! Tôi không biết rằng dấu / có thể bị bỏ lại trong bashbiểu thức này ...
TrueY

4

Trên thực tế awkcó một tính năng gọi là liên kết 'Biến phân tách trường đầu vào' . Đây là cách sử dụng nó. Nó không thực sự là một mảng, nhưng nó sử dụng các biến $ nội bộ. Để tách một chuỗi đơn giản thì dễ dàng hơn.

echo "12|23|11" | awk 'BEGIN {FS="|";} { print $1, $2, $3 }'

3
echo "12|23|11" | awk '{split($0,a,"|"); print a[3] a[2] a[1]}'

nên làm việc.



1

Trò đùa? :)

Làm thế nào về echo "12|23|11" | awk '{split($0,a,"|"); print a[3] a[2] a[1]}'

Đây là đầu ra của tôi:

p2> echo "12|23|11" | awk '{split($0,a,"|"); print a[3] a[2] a[1]}'
112312

Vì vậy, tôi đoán rằng nó hoạt động sau tất cả ..


đó có phải là do độ dài của chuỗi không? kể từ đó, độ dài chuỗi của tôi là 4000. mọi ý tưởng
Mohamed Saligh

1

Tôi biết đây là loại câu hỏi cũ, nhưng tôi nghĩ có lẽ ai đó thích mánh khóe của tôi. Đặc biệt vì giải pháp này không giới hạn ở một số mặt hàng cụ thể.

# Convert to an array
_ITEMS=($(echo "12|23|11" | tr '|' '\n'))

# Output array items
for _ITEM in "${_ITEMS[@]}"; do
  echo "Item: ${_ITEM}"
done

Đầu ra sẽ là:

Item: 12
Item: 23
Item: 11
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.