Tôi có nhiều tệp được sắp xếp theo tên tệp trong một thư mục. Tôi muốn sao chép các tệp N (giả sử, N = 4) cuối cùng vào thư mục chính của tôi. Tôi nên làm thế nào?
cp ./<the final 4 files> ~/
Tôi có nhiều tệp được sắp xếp theo tên tệp trong một thư mục. Tôi muốn sao chép các tệp N (giả sử, N = 4) cuối cùng vào thư mục chính của tôi. Tôi nên làm thế nào?
cp ./<the final 4 files> ~/
Câu trả lời:
Điều này có thể dễ dàng thực hiện với mảng bash / ksh93 / zsh:
a=(*)
cp -- "${a[@]: -4}" ~/
Điều này hoạt động cho tất cả các tên tệp không bị ẩn ngay cả khi chúng chứa khoảng trắng, tab, dòng mới hoặc các ký tự khó khác (giả sử có ít nhất 4 tệp không bị ẩn trong thư mục hiện tại với bash
).
a=(*)
Điều này tạo ra một mảng a
với tất cả các tên tệp. Tên tệp được trả về bởi bash được sắp xếp theo thứ tự abc. (Tôi cho rằng đây là ý của bạn khi "sắp xếp theo tên tệp." )
${a[@]: -4}
Điều này trả về bốn phần tử cuối cùng của mảng a
(với điều kiện mảng chứa ít nhất 4 phần tử với bash
).
cp -- "${a[@]: -4}" ~/
Điều này sao chép bốn tên tập tin cuối cùng vào thư mục nhà của bạn.
Điều này sẽ chỉ sao chép bốn tệp cuối cùng vào thư mục chính và đồng thời, thêm chuỗi a_
vào tên của tệp đã sao chép:
a=(*)
for fname in "${a[@]: -4}"; do cp -- "$fname" ~/a_"$fname"; done
Nếu chúng ta sử dụng a=(./some_dir/*)
thay vì a=(*)
, thì chúng ta có vấn đề về thư mục được gắn vào tên tệp. Một giải pháp là:
a=(./some_dir/*)
for f in "${a[@]: -4}"; do cp "$f" ~/a_"${f##*/}"; done
Một giải pháp khác là sử dụng một subshell và cd
vào thư mục trong subshell:
(cd ./some_dir && a=(*) && for f in "${a[@]: -4}"; do cp -- "$f" ~/a_"$f"; done)
Khi subshell hoàn thành, shell sẽ đưa chúng ta trở lại thư mục gốc.
Câu hỏi yêu cầu các tệp "được sắp xếp theo tên tệp". Thứ tự đó, Olivier Dulac chỉ ra trong các bình luận, sẽ thay đổi từ địa phương này sang địa phương khác. Nếu điều quan trọng là có kết quả cố định độc lập với cài đặt máy, thì tốt nhất là chỉ định rõ ràng miền địa phương khi mảng a
được xác định. Ví dụ:
LC_ALL=C a=(*)
Bạn có thể tìm ra địa điểm mà bạn hiện đang ở bằng cách chạy locale
lệnh.
Nếu bạn đang sử dụng, zsh
bạn có thể gửi kèm trong ngoặc đơn ()
một danh sách cái gọi là vòng loại toàn cầu để chọn các tệp mong muốn. Trong trường hợp của bạn, đó sẽ là
cp *(On[1,4]) ~/
Ở đây On
sắp xếp tên tệp theo thứ tự abc theo thứ tự ngược lại và [1,4]
chỉ mất 4 trong số chúng.
Bạn có thể làm cho điều này mạnh mẽ hơn bằng cách chỉ chọn các tệp đơn giản (không bao gồm các thư mục, đường ống, v.v.) .
và cũng bằng cách thêm --
vào cp
lệnh để xử lý các tệp có tên bắt đầu -
đúng, vì vậy:
cp -- *(.On[1,4]) ~
Thêm D
vòng loại nếu bạn cũng muốn xem xét các tệp ẩn (tệp chấm):
cp -- *(D.On[1,4]) ~
*([-4,-1])
.
on
) là hành vi mặc định, do đó không cần chỉ định điều này và -
trước các số đếm tên tệp từ phía dưới.
Đây là một giải pháp sử dụng các lệnh bash cực kỳ đơn giản.
find . -maxdepth 1 -type f | sort | tail -n 4 | while read -r file; do cp "$file" ~/; done
Giải trình:
find . -maxdepth 1 -type f
Tìm tất cả các tập tin trong thư mục hiện tại.
sort
Sắp xếp theo thứ tự abc.
tail -n 4
Chỉ hiển thị 4 dòng cuối cùng.
while read -r file; do cp "$file" ~/; done
Vòng lặp trên mỗi dòng thực hiện lệnh sao chép.
Vì vậy, miễn là bạn tìm thấy loại vỏ dễ chịu, bạn có thể làm:
set -- /path/to/source/dir/*
[ "$#" -le 4 ] || shift "$(($#-4))"
cp "$@" /path/to/target/dir
Điều này rất giống với bash
giải pháp mảng cụ thể được cung cấp, nhưng phải có thể mang theo mọi vỏ tương thích POSIX. Một số lưu ý về cả hai phương pháp:
Điều quan trọng là bạn dẫn dắt các cp
đối số của mình w / cp --
hoặc bạn nhận được một trong hai .
dấu chấm hoặc /
ở đầu mỗi tên. Nếu bạn không làm điều này, bạn có nguy cơ một -
dấu gạch đầu dòng trong cp
đối số đầu tiên có thể được hiểu là một tùy chọn và khiến thao tác không thành công hoặc làm cho kết quả không mong muốn.
set -- ./*
hoặc array=(./*)
.Điều quan trọng khi sử dụng cả hai phương pháp để đảm bảo bạn có ít nhất nhiều mục trong mảng arg khi bạn cố gắng loại bỏ - Tôi làm điều đó ở đây với một mở rộng toán học. Điều này shift
chỉ xảy ra nếu có ít nhất 4 mục trong mảng arg - và sau đó chỉ loại bỏ những đối số đầu tiên tạo ra thặng dư 4 ..
set 1 2 3 4 5
trường hợp, nó sẽ thay đổi 1
đi, nhưng trong một set 1 2 3
trường hợp, nó sẽ không thay đổi gì.a=(1 2 3); echo "${a[@]: -4}"
in một dòng trống.Nếu bạn đang sao chép từ thư mục này sang thư mục khác, bạn có thể sử dụng pax
:
set -- /path/to/source/dir/*
[ "$#" -le 4 ] || shift "$(($#-4))"
pax -rws '|.*/|new_prefix|' "$@" /path/to/target/dir
... sẽ áp dụng sed
thay thế kiểu cho tất cả tên tệp khi nó sao chép chúng.
Nếu chỉ có các tệp và tên của chúng không chứa khoảng trắng hoặc dòng mới (và bạn chưa sửa đổi $IFS
) hoặc ký tự toàn cầu (hoặc một số ký tự không in được với một số triển khai ls
) và không bắt đầu .
, thì bạn có thể làm điều này :
cp -- $(ls | tail -n 4) ~/
a_
vào TẤT CẢ bốn tệp không? Tôi đã thử echo "a_$(ls | tail -n 4)"
, nhưng nó chỉ chuẩn bị các tập tin đầu tiên.
ls
đầu ra phân tích cú pháp được coi là hình thức xấu vì nó thường thất bại khủng khiếp đối với các tên tệp không chỉ chứa khoảng trắng, mà còn các tab, dòng mới hoặc các ký tự hợp lệ nhưng "khó" khác.
Đây là một giải pháp bash đơn giản mà không cần bất kỳ mã vòng lặp. Sao chép 4 tập tin cuối cùng từ thư mục hiện tại đến đích:
$ find . -maxdepth 1 -type f |tail -n -4|xargs cp -t "$destdir"
find
cung cấp cho bạn các tập tin theo thứ tự mong muốn? Làm thế nào để bạn đảm bảo rằng các tệp chứa các ký tự khoảng trắng (bao gồm cả dòng mới) trong tên của chúng được xử lý chính xác?
LC_ALL=C