Sử dụng thay thế tham số trên mảng Bash


8

Tôi có file.txt mà tôi cần đọc vào một mảng Bash. Sau đó, tôi cần xóa dấu cách, dấu ngoặc kép và tất cả trừ dấu phẩy đầu tiên trong mỗi mục . Đây là bao xa tôi đã nhận được:

$ cat file.txt
10,this
2 0 , i s
30,"all"
40,I
50,n,e,e,d,2
60",s e,e"

$ cat script.sh
#!/bin/bash
readarray -t ARRAY<$1
ARRAY=( "${ARRAY[@]// /}" )
ARRAY=( "${ARRAY[@]//\"/}" )
for ELEMENT in "${ARRAY[@]}";do
    echo "|ELEMENT|$ELEMENT|"
done

$ ./script.sh file.txt
|ELEMENT|10,this|
|ELEMENT|20,is|
|ELEMENT|30,all|
|ELEMENT|40,I|
|ELEMENT|50,n,e,e,d,2|
|ELEMENT|60,se,e|

Mà hoạt động tuyệt vời ngoại trừ các dấu phẩy. Tôi biết rằng có nhiều cách để nuôi con mèo này, nhưng do tập lệnh lớn hơn nên đây là một phần của, tôi thực sự muốn sử dụng thay thế tham số để đến đây:

|ELEMENT|10,this|
|ELEMENT|20,is|
|ELEMENT|30,all|
|ELEMENT|40,I|
|ELEMENT|50,need2|
|ELEMENT|60,see|

Điều này có thể thông qua thay thế tham số?


3
Có bất kỳ lý do nào bạn cần giữ văn bản trong một mảng không, và tại sao bạn không thể cho phép awkhoặc sedxử lý dữ liệu?
Kusalananda

@Jeff - Vòng lặp trên mảng sẽ là một cơn ác mộng khi thực hiện trong kịch bản lớn hơn mà tôi đang làm việc.
Jon Red

3
@JonRed Tôi không biết bạn đang làm gì, vì vậy hoàn toàn có thể bạn không có sự lựa chọn nào trong vấn đề này, nhưng nói chung, khi bạn thấy mình thực hiện những cú nhào lộn chuỗi phức tạp như vậy trong vỏ, đó là một dấu hiệu rất tốt cho bạn nên sử dụng một ngôn ngữ lập trình thực tế. Shell không được thiết kế như một ngôn ngữ lập trình và trong khi nó có thể được sử dụng như một ngôn ngữ, nó thực sự không phải là một ý tưởng tốt cho những thứ phức tạp hơn. Tôi đặc biệt khuyên bạn nên xem xét chuyển sang perl hoặc python hoặc bất kỳ ngôn ngữ kịch bản nào khác.
terdon

@terdon Thật buồn cười, tôi vừa nói xong gần như chính xác điều đó với đồng nghiệp của tôi trước khi tôi đọc bài đăng này. Về cơ bản tôi đã nói đây là phiên bản cuối cùng của kịch bản này và rằng bất kỳ yêu cầu nào khác sẽ bắt buộc phải viết lại bằng Perl. Vì vậy, vâng, tôi hoàn toàn đồng ý
Jon Red

Câu trả lời:


9

Tôi sẽ loại bỏ những gì bạn cần loại bỏ bằng cách sử dụng sed trước khi tải vào mảng (cũng lưu ý các tên biến chữ thường, nói chung, tốt nhất là tránh các biến viết hoa trong tập lệnh shell):

#!/bin/bash
readarray -t array< <(sed 's/"//g; s/  *//g; s/,/"/; s/,//g; s/"/,/' "$1")
for element in "${array[@]}";do
    echo "|ELEMENT|$element|"
done

Điều này tạo ra đầu ra sau trên tệp ví dụ của bạn:

$ foo.sh file 
|ELEMENT|10,this|
|ELEMENT|20,is|
|ELEMENT|30,all|
|ELEMENT|40,I|
|ELEMENT|50,need2|
|ELEMENT|60,see|

Nếu bạn thực sự phải sử dụng thay thế tham số, hãy thử một cái gì đó như thế này:

#!/bin/bash
readarray -t array< "$1"
array=( "${array[@]// /}" )
array=( "${array[@]//\"/}" )
array=( "${array[@]/,/\"}" )
array=( "${array[@]//,/}" )
array=( "${array[@]/\"/,}" )

for element in "${array[@]}"; do
    echo "|ELEMENT|$element|"
done

1
@JonRed Tôi đã thêm một phiên bản thay thế tham số nhưng nó phức tạp, cồng kềnh và xấu xí. Làm điều này trong vỏ rất hiếm khi là một ý tưởng tốt.
terdon

1
Lưu ý rằng nếu bạn đã xóa cả dấu cách và dấu ngoặc kép, các ký tự này có sẵn để sử dụng thay cho dấu ngoặc kép RANDOMTEXTTHATWILLNEVERBEINTHEFILE.
Kusalananda

1
@Kusalananda yeah, tôi vừa đọc câu trả lời của bạn. Nên nghĩ về điều đó! Cảm ơn :)
terdon

Trả lời trực tiếp câu hỏi, minh họa tại sao giải pháp ưa thích của tôi không lý tưởng và cung cấp giải pháp thay thế khả thi nhất. Bạn thắng, trả lời tốt nhất.
Jon Red

10

Theo như tôi có thể thấy, không cần phải đọc nó thành một bashmảng để tạo đầu ra đó:

$ sed 's/[ "]//g; s/,/ /; s/,//g; s/ /,/; s/.*/|ELEMENT|&|/' <file
|ELEMENT|10,this|
|ELEMENT|20,is|
|ELEMENT|30,all|
|ELEMENT|40,I|
|ELEMENT|50,need2|
|ELEMENT|60,see|

Các sedbiểu hiện xóa khoảng trắng và dấu ngoặc kép, thay thế các dấu phẩy đầu tiên với một không gian (không có dấu cách khác trong chuỗi vào thời điểm này), xóa tất cả các dấu phẩy khác, phục hồi các dấu phẩy đầu tiên, và prepends và gắn thêm các dữ liệu thêm.

Ngoài ra, với GNU sed:

sed 's/[ "]//g; s/,//2g; s/.*/|ELEMENT|&|/' <file

(tiêu chuẩn sedkhông hỗ trợ kết hợp 2glàm cờ cho slệnh).


1
với GNU sed, bạn có thể sử dụng 's/,//2gđể xóa dấu phẩy, bắt đầu bằng
jackman

2
Và, 2 lệnh /// cuối cùng có thể s/.*/|ELEMENT|&|/nhưng đó có thể là nỗ lực nhiều hơn cho sed.
glenn jackman

1
@glennjackman Có thể, nhưng nó trông khá gọn gàng.
Kusalananda

Vâng, đây là một phần của một kịch bản lớn hơn. Các mảng là cần thiết, không chỉ cho đầu ra. Do đó tôi quan tâm đến việc thay thế tham số. Tôi có thể lặp lại mảng này nhưng điều đó sẽ là một cơn ác mộng khi thực hiện. Terndon đã cung cấp một giải pháp không vòng lặp bằng cách sử dụng sed mà tôi có khả năng sẽ quay trở lại nếu việc thay thế tham số là không nên.
Jon Red

Tuy nhiên, nếu tôi không bị ràng buộc với việc sử dụng một mảng, đây sẽ là giải pháp tốt nhất.
Jon Red

9
ELEMENT='50,n,e,e,d,2'
IFS=, read -r first rest <<<"$ELEMENT"
printf "%s,%s\n" "$first" "${rest//,/}"
50,need2

Thoát khỏi thói quen sử dụng tên biến ALLCAPS. Cuối cùng, bạn sẽ va chạm với một biến "hệ thống" quan trọng như PATH và phá vỡ mã của bạn.


Không thay thế tham số. NHƯNG, tôi đã không biết rằng tên biến ALLCAPS là một thói quen xấu ở Bash. Bạn làm cho một điểm tốt, một điều mà một người nguyền rủa chắc chắn xác nhận. Cảm ơn bạn đã cải thiện phong cách của tôi! :)
Jon Red

1
Tôi đã trả lời câu hỏi mà người đó đã viết PATH=something; ls $PATHvà sau đó tự hỏi về ls: command not foundlỗi này.
glenn jackman

1
Có gần một trăm biến tích hợp được đặt tên trong tất cả các mũ (nhấp qua liên kết trang người đàn ông này ) để xem ...
Jeff Schaller

8

[Đây thực chất là một phiên bản phát triển đầy đủ hơn của câu trả lời của glenn jackmann ]

Xây dựng một mảng kết hợp từ khóa và giá trị bị tước, sử dụng dấu phẩy đầu tiên làm dấu phân cách:

declare -A arr
while IFS=, read -r k v; do arr["${k//[ \"]}"]="${v//[ ,\"]}"; done < file.txt
for k in "${!arr[@]}"; do 
  printf '|ELEMENT|%s,%s|\n' "$k" "${arr[$k]}"
done
|ELEMENT|20,is|
|ELEMENT|10,this|
|ELEMENT|50,need2|
|ELEMENT|40,I|
|ELEMENT|60,see|
|ELEMENT|30,all|

6

Bạn có thể lặp qua mảng và sử dụng một biến trung gian:

for((i=0; i < "${#ARRAY[@]}"; i++))
do
  rest="${ARRAY[i]#*,}"
  ARRAY[i]="${ARRAY[i]%%,*}","${rest//,/}"
done

Điều này gán cho restphần sau dấu phẩy đầu tiên; sau đó chúng ta ghép ba mảnh lại thành biến ban đầu:

  • phần trước dấu phẩy đầu tiên
  • dấu phẩy
  • sự thay thế trong restmỗi dấu phẩy không có gì

Đây là suy nghĩ đầu tiên của tôi và đủ đơn giản cho ví dụ nhưng đây là một phần của tập lệnh lớn hơn trong đó mảng rất lớn và đã có các vòng lặp và nó sẽ là toàn bộ. Điều này chắc chắn sẽ làm việc nhưng sẽ rất cồng kềnh để thực hiện trong dự án lớn hơn mà tôi đang làm.
Jon Red

1
Đủ công bằng; Tôi chỉ cố gắng trả lời trong giới hạn (chỉ mở rộng tham số).
Jeff Schaller
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.