Làm cách nào để lưu trữ kết quả lệnh “find” dưới dạng một mảng trong Bash


93

Tôi đang cố gắng lưu kết quả từ finddưới dạng mảng. Đây là mã của tôi:

#!/bin/bash

echo "input : "
read input

echo "searching file with this pattern '${input}' under present directory"
array=`find . -name ${input}`

len=${#array[*]}
echo "found : ${len}"

i=0

while [ $i -lt $len ]
do
echo ${array[$i]}
let i++
done

Tôi nhận được 2 tệp .txt trong thư mục hiện tại. Vì vậy, tôi mong đợi '2' là kết quả của ${len}. Tuy nhiên, nó in ra 1. Lý do là nó nhận tất cả kết quả findlà một phần tử. Làm thế nào tôi có thể sửa lỗi này?

Tái bút
Tôi đã tìm thấy một số giải pháp trên StackOverFlow về một vấn đề tương tự. Tuy nhiên, chúng hơi khác một chút nên tôi không thể áp dụng trong trường hợp của mình. Tôi cần lưu trữ kết quả trong một biến trước vòng lặp. Cảm ơn một lần nữa.

Câu trả lời:


134

Cập nhật 2020 cho người dùng Linux:

Nếu bạn có một phiên bản up-to-date của bash (4,4-alpha hoặc tốt hơn), như bạn có thể làm gì nếu bạn đang trên Linux, sau đó bạn nên sử dụng câu trả lời Benjamin W. .

Nếu bạn đang sử dụng Mac OS, mà — lần trước tôi đã kiểm tra — vẫn sử dụng bash 3.2 hoặc đang sử dụng bash cũ hơn, thì hãy tiếp tục sang phần tiếp theo.

Câu trả lời cho bash 4.3 trở xuống

Đây là một giải pháp để nhận đầu ra của findmột bashmảng:

array=()
while IFS=  read -r -d $'\0'; do
    array+=("$REPLY")
done < <(find . -name "${input}" -print0)

Điều này khá phức tạp vì nói chung, tên tệp có thể có khoảng trắng, dòng mới và các ký tự thù địch với tập lệnh khác. Cách duy nhất để sử dụng findvà để các tên tệp được tách biệt với nhau một cách an toàn là sử dụng cách -print0in các tên tệp được phân tách bằng ký tự rỗng. Điều này sẽ không có gì bất tiện nếu bash's readarray/ mapfilefunctions hỗ trợ các chuỗi được phân tách bằng null nhưng chúng thì không. Bash's readlàm và điều đó dẫn chúng ta đến vòng lặp ở trên.

[Câu trả lời này ban đầu được viết vào năm 2014. Nếu bạn có phiên bản bash gần đây, vui lòng xem bản cập nhật bên dưới.]

Làm thế nào nó hoạt động

  1. Dòng đầu tiên tạo một mảng trống: array=()

  2. Mỗi khi readcâu lệnh được thực thi, tên tệp được phân tách bằng null sẽ được đọc từ đầu vào chuẩn. Các -rtùy chọn cho readrời ký tự gạch chéo ngược một mình. Cho -d $'\0'biết readrằng đầu vào sẽ được phân tách bằng null. Kể từ khi chúng tôi bỏ qua tên để read, vỏ đặt đầu vào cho tên mặc định: REPLY.

  3. Câu array+=("$REPLY")lệnh nối tên tệp mới vào mảng array.

  4. Dòng cuối cùng kết hợp chuyển hướng và thay thế lệnh để cung cấp đầu ra findcho đầu vào chuẩn của whilevòng lặp.

Tại sao sử dụng thay thế quy trình?

Nếu chúng tôi không sử dụng thay thế quy trình, vòng lặp có thể được viết là:

array=()
find . -name "${input}" -print0 >tmpfile
while IFS=  read -r -d $'\0'; do
    array+=("$REPLY")
done <tmpfile
rm -f tmpfile

Trong phần trên, đầu ra của findđược lưu trữ trong một tệp tạm thời và tệp đó được sử dụng làm đầu vào tiêu chuẩn cho vòng lặp while. Ý tưởng của việc thay thế quy trình là làm cho các tệp tạm thời như vậy không cần thiết. Vì vậy, thay vì để whilevòng lặp lấy stdin của nó tmpfile, chúng ta có thể yêu cầu nó lấy stdin từ đó <(find . -name ${input} -print0).

Thay thế quy trình rất hữu ích. Ở nhiều nơi mà lệnh muốn đọc từ tệp, bạn có thể chỉ định thay thế tiến trình <(...), thay vì tên tệp. Có một dạng tương tự, >(...)có thể được sử dụng thay cho tên tệp nơi lệnh muốn ghi vào tệp.

Giống như mảng, thay thế quy trình là một tính năng của bash và các trình bao nâng cao khác. Nó không phải là một phần của tiêu chuẩn POSIX.

Thay thế: lastpipe

Nếu muốn, lastpipecó thể được sử dụng thay thế cho quá trình thay thế (đầu mũ: Caesar ):

set +m
shopt -s lastpipe
array=()
find . -name "${input}" -print0 | while IFS=  read -r -d $'\0'; do array+=("$REPLY"); done; declare -p array

shopt -s lastpipeyêu cầu bash chạy lệnh cuối cùng trong đường dẫn trong trình bao hiện tại (không phải nền). Bằng cách này, phần arraycòn lại tồn tại sau khi đường ống hoàn thành. Bởi vì lastpipechỉ có hiệu lực nếu điều khiển công việc bị tắt, chúng tôi chạy set +m. (Trong một tập lệnh, trái ngược với dòng lệnh, điều khiển công việc bị tắt theo mặc định.)

Ghi chú bổ sung

Lệnh sau tạo một biến shell, không phải một mảng shell:

array=`find . -name "${input}"`

Nếu bạn muốn tạo một mảng, bạn sẽ cần đặt các parens xung quanh đầu ra của tìm kiếm. Vì vậy, một cách ngây thơ, người ta có thể:

array=(`find . -name "${input}"`)  # don't do this

Vấn đề là trình bao thực hiện tách từ trên các kết quả findđể các phần tử của mảng không được đảm bảo là những gì bạn muốn.

Cập nhật 2019

Bắt đầu với phiên bản 4.4-alpha, bash hiện hỗ trợ một -dtùy chọn để vòng lặp trên không còn cần thiết nữa. Thay vào đó, người ta có thể sử dụng:

mapfile -d $'\0' array < <(find . -name "${input}" -print0)

Để biết thêm thông tin về vấn đề này, vui lòng xem (và upvote) câu trả lời Benjamin W. .


1
@JuneyoungOh Rất vui vì nó đã giúp ích. Tôi đã thêm một phần thay thế quy trình.
John1024,

3
@Rockallite Đó là một quan sát tốt nhưng không đầy đủ. Mặc dù đúng là chúng ta không chia thành nhiều từ, chúng ta vẫn cần IFS=tránh loại bỏ khoảng trắng từ đầu hoặc cuối của các dòng nhập. Bạn có thể kiểm tra điều này dễ dàng bằng cách so sánh đầu ra của read var <<<' abc '; echo ">$var<"với đầu ra của IFS= read var <<<' abc '; echo ">$var<". Trong trường hợp trước đây, các khoảng trắng trước và sau abcbị xóa. Trong trường hợp thứ hai, họ không. Tên tệp bắt đầu hoặc kết thúc bằng khoảng trắng có thể không bình thường, nhưng chúng tồn tại, chúng tôi muốn chúng được xử lý chính xác.
John1024,

1
Hi, sau khi tôi thực thi mã của bạn tôi nhận được tin nhắn cú pháp lỗi gần bất ngờ thẻ <' thực hiện <<(tìm aaa / -không -newermt "$ last_build_timestamp_v" -type f print0)'
Przemysław Sienkiewicz

1
Một lưu ý: đơn giản ''có thể được sử dụng thay vì $'\0':n=0; while IFS= read -r -d '' line || [ "$line" ]; do echo "$((++n)):$line"; done < <(printf 'first\nstill first\0second\0third')
glenn jackman

1
@theeagle Tôi giả sử rằng bạn định viết BLAH=$(find . -name '*.php'). Như đã thảo luận trong câu trả lời, cách tiếp cận đó sẽ hoạt động trong một số trường hợp hạn chế nhưng nó sẽ không hoạt động nói chung với tất cả các tên tệp và nó không tạo ra một mảng như OP mong đợi .
John1024,

35

Bash 4.4 đã giới thiệu một -dtùy chọn cho readarray/ mapfile, vì vậy điều này hiện có thể được giải quyết bằng

readarray -d '' array < <(find . -name "$input" -print0)

cho một phương thức hoạt động với các tên tệp tùy ý bao gồm khoảng trống, dòng mới và các ký tự dấu chấm. Điều này yêu cầu findhỗ trợ của bạn -print0, chẳng hạn như GNU find.

Từ hướng dẫn sử dụng (bỏ qua các tùy chọn khác):

mapfile [-d delim] [array]

-d
Ký tự đầu tiên của delimđược sử dụng để kết thúc mỗi dòng nhập, thay vì dòng mới. Nếu delimlà chuỗi trống, mapfilesẽ kết thúc một dòng khi nó đọc một ký tự NUL.

readarraychỉ là một từ đồng nghĩa của mapfile.


18

Nếu bạn đang sử dụng bash4 trở lên, bạn có thể thay thế việc sử dụng findbằng

shopt -s globstar nullglob
array=( **/*"$input"* )

Các **mô hình được kích hoạt bởi globstarcác trận đấu 0 hoặc nhiều thư mục, cho phép mô hình để phù hợp với độ sâu tùy ý trong thư mục hiện hành. Nếu không có nullglobtùy chọn, mẫu (sau khi mở rộng tham số) được xử lý theo nghĩa đen, vì vậy nếu không có kết quả phù hợp, bạn sẽ có một mảng với một chuỗi duy nhất chứ không phải là một mảng trống.

Thêm dotglobtùy chọn vào dòng đầu tiên nếu bạn muốn duyệt qua các thư mục ẩn (như .ssh) và khớp với các tệp ẩn (như .bashrc).


4
Có thể nullglobquá…
kojiro

1
Yeah, tôi luôn quên điều đó.
chepner

5
Lưu ý rằng điều này sẽ không bao gồm các tệp và thư mục ẩn, trừ khi dotglobđược thiết lập (điều này có thể muốn hoặc có thể không, nhưng cũng đáng nói).
gniourf_gniourf

10

bạn có thể thử một cái gì đó như

array=(`find . -type f | sort -r | head -2`)
và để in các giá trị mảng, bạn có thể thử một cái gì đó như echo "${array[*]}"


8
Dấu ngắt nếu có tên tệp có dấu cách hoặc ký tự hình cầu.
gniourf_gniourf

1

Cách sau dường như hoạt động cho cả Bash và Z Shell trên macOS.

#! /bin/sh

IFS=$'\n'
paths=($(find . -name "foo"))
unset IFS

printf "%s\n" "${paths[@]}"

Điều này hoạt động với các tệp có khoảng trắng và các ký tự đặc biệt khác, không thành công với trường hợp (phải thừa nhận là hiếm) các tệp có dấu ngắt dòng trong tên của chúng. Bạn có thể tạo một bài kiểm tra vớiprintf "%b" "file name with spaces, a star * ...\012and a second line\0" | xargs -0 touch
Stéphane Gourichon

-1

Trong bash, $(<any_shell_cmd>)giúp chạy một lệnh và nắm bắt đầu ra. Chuyển điều này đến IFSvới \nas delimiter giúp chuyển đổi nó thành một mảng.

IFS='\n' read -r -a txt_files <<< $(find /path/to/dir -name "*.txt")

4
Điều này sẽ chỉ nhận được tệp đầu tiên của kết quả trong findmảng.
Benjamin W.

-2

Bạn có thể làm như thế này:

#!/bin/bash
echo "input : "
read input

echo "searching file with this pattern '${input}' under present directory"
array=(`find . -name '*'${input}'*'`)

for i in "${array[@]}"
do :
    echo $i
done

1
Cảm ơn. rất nhiều. Nhưng như @anishsane đã chỉ ra, các khoảng trống trong tên tệp nên được xem xét trong chương trình của tôi. Dù sao cũng cảm ơn!
Juneyoung Oh

-3

Đối với tôi, điều này hoạt động tốt trên cygwin:

declare -a names=$(echo "("; find <path> <other options> -printf '"%p" '; echo ")")
for nm in "${names[@]}"
do
    echo "$nm"
done

Điều này hoạt động với dấu cách, nhưng không hoạt động với dấu ngoặc kép (") trong tên thư mục (dù sao cũng không được phép trong môi trường Windows).

Cẩn thận với khoảng trống trong tùy chọn -printf.


3
Bị hỏng và nguy hiểm : sẽ không xử lý báo giá, và bị tiêm mã tùy ý. KHÔNG ĐƯỢC DÙNG.
gniourf_gniourf

2
Có vẻ như ai đó đã gắn cờ bài đăng này để xóa. "Nó là sai" không phải là một lý do để xóa trên SO. Người dùng đã cố gắng trả lời, đó là chủ đề và đáp ứng các tiêu chí cho câu trả lời. Nút downvote được sử dụng để đo mức độ hữu ích và đúng đắn, không phải nút xóa.
Frambot

3
Như gniourf đã chỉ ra, nó không dành cho những môi trường mà người khác nhập các tùy chọn trên hệ thống của bạn, ví dụ như các trang web. Nhưng không phải ai cũng lập trình cho môi trường đó. Tôi đã sử dụng nó để đổi tên tệp trong thư mục.
R Risack
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.