Cách tuân thủ POSIX để làm việc với danh sách tên tệp có thể có khoảng trắng


14

Tôi đã thấy các hướng dẫn kịch bản Bash đề xuất sử dụng mảng để làm việc với tên tệp có chứa khoảng trắng. Tuy nhiên, DashAsBinSh gợi ý rằng các mảng không thể di động được nên tôi đang tìm cách làm việc tuân thủ POSIX với danh sách tên tệp có thể chứa khoảng trắng.

Tôi đang tìm cách sửa đổi kịch bản ví dụ dưới đây để nó sẽ echo

foo/target/a.jar
foo/target/b.jar
bar/target/lol whitespace.jar

Đây là kịch bản

#!/usr/bin/env sh

INPUT="foo/target/a.jar
foo/target/b.jar
bar/target/b.jar
bar/target/lol whitespace.jar"
# this would be produced by a 'ls' command
# We can execute the ls within the script, if it helps

dostuffwith() { echo $1; };

F_LOCATIONS=$INPUT
ALL_FILES=$(for f in $F_LOCATIONS; do echo `basename $f`; done)
ALL_FILES=$(echo "$ALL_FILES" | sort | uniq)

for f in $ALL_FILES
do
    fpath=$(echo "$F_LOCATIONS" | grep -m1 $f)
    dostuffwith $fpath
done

Câu trả lời:


8

POSIX vỏ có một mảng: các tham số vị trí ( $1, $2vv, refered chung là "$@").

set -- 'foo/target/a.jar' 'foo/target/b.jar' 'bar/target/b.jar' 'bar/target/lol whitespace.jar'
set -- "$@" '/another/one at the end.jar'

for jar do
  dostuffwith "$jar"
done

Điều này là bất tiện vì chỉ có một, và nó phá hủy mọi sử dụng khác của các tham số vị trí. Các tham số vị trí là cục bộ của một chức năng, đôi khi là một phước lành và đôi khi là một lời nguyền.

Nếu tên tệp của bạn được đảm bảo không chứa dòng mới, bạn có thể sử dụng dòng mới làm dấu phân cách. Khi bạn mở rộng biến, trước tiên hãy tắt tính năng toàn cầu hóa set -fvà thiết lập danh sách các ký tự phân tách trường IFSchỉ chứa một dòng mới.

INPUT="foo/target/a.jar
foo/target/b.jar
bar/target/b.jar
bar/target/lol whitespace.jar"

set -f; IFS='
'                           # turn off variable value expansion except for splitting at newlines
for jar in $INPUT; do
  set +f; unset IFS
  dostuffwith "$jar"        # restore globbing and field splitting at all whitespace
done
set +f; unset IFS           # do it again in case $INPUT was empty

Với các mục trong danh sách của bạn được phân tách bằng các dòng mới, đặc biệt, bạn có thể sử dụng nhiều lệnh xử lý văn bản một cách hữu ích sort.

Hãy nhớ luôn đặt dấu ngoặc kép xung quanh các thay thế khác nhau, ngoại trừ khi bạn rõ ràng muốn phân tách trường xảy ra (cũng như toàn cầu hóa, trừ khi bạn tắt nó đi).


Câu trả lời và giải thích tốt. Tôi sẽ đánh dấu điều này là được chấp nhận vì điều này làm cho sort | uniqbước ban đầu hoạt động như dự định.
Eero Aaltonen

5

$INPUTbiến của bạn sử dụng dòng mới làm dấu phân cách, tôi sẽ giả định rằng các tệp của bạn sẽ không có dòng mới trong tên. Như vậy, vâng, có một cách đơn giản để lặp lại các tệp và duy trì khoảng trắng.

Ý tưởng là sử dụng readvỏ dựng sẵn. Thông thường readsẽ phân chia trên bất kỳ khoảng trắng nào, và do đó, không gian sẽ phá vỡ nó. Nhưng bạn có thể thiết lập IFS=$'\n'và thay vào đó nó sẽ chỉ phân chia trên các dòng mới. Vì vậy, bạn có thể lặp lại qua từng dòng trong danh sách của bạn.

Đây là giải pháp nhỏ nhất tôi có thể đưa ra:

INPUT="foo/target/a.jar
foo/target/b.jar
bar/target/b.jar
bar/target/lol whitespace.jar"

dostuffwith() {
    echo "$1"
}

echo "$INPUT" | awk -F/ '{if (!seen[$NF]++) print }' | \
while IFS=$'\n' read file; do
  dostuffwith "$file"
done

Về cơ bản, nó sẽ gửi "$ INPUT" để sao awkchép dựa trên tên tệp (nó tách ra /và sau đó in dòng nếu mục cuối cùng chưa được nhìn thấy trước đó). Sau đó, một khi awk đã tạo danh sách các đường dẫn tệp, chúng tôi sử dụng while readđể lặp qua danh sách.


$ checkbashism bar.sh bashism có thể có trong bar.sh dòng 14 (chuỗi <<< ở đây)
Eero Aaltonen

1
@EeroAaltonen Thay đổi nó để không sử dụng herestring. Lưu ý rằng với sự thay đổi này, whilevòng lặp và do đó dostuffwithđược thực hiện trong một lớp con. Vì vậy, bất kỳ biến hoặc thay đổi được thực hiện cho shell đang chạy sẽ bị mất khi vòng lặp hoàn thành. Thay thế duy nhất là sử dụng một di sản đầy đủ, điều đó không gây khó chịu, nhưng tôi nghĩ rằng điều này sẽ tốt hơn.
Patrick

Tôi đang trao giải thưởng dựa trên khả năng dễ đọc hơn là nhỏ. Điều này chắc chắn hoạt động và đã +1 cho điều đó.
Eero Aaltonen

IFS="\n"chia tách trên dấu gạch chéo ngược và n ký tự. Nhưng trong read file, không có sự chia tách. IFS="\n"vẫn hữu ích ở chỗ nó loại bỏ các ký tự trống khỏi $ IFS mà nếu không thì sẽ bị tước ở đầu và cuối đầu vào. Để đọc một dòng, cú pháp chính tắc là IFS= read -r line, mặc dù IFS=anything read -r line(miễn là mọi thứ không chứa khoảng trống) cũng sẽ hoạt động.
Stéphane Chazelas

Giáo sư. Không chắc chắn làm thế nào tôi quản lý cái đó. Đã sửa.
Patrick
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.