Mam wiele plików uporządkowanych według nazw plików w katalogu. Chcę skopiować ostatnie N (powiedzmy N = 4) plików do mojego katalogu domowego. Jak mam to zrobić?
cp ./<the final 4 files> ~/
Mam wiele plików uporządkowanych według nazw plików w katalogu. Chcę skopiować ostatnie N (powiedzmy N = 4) plików do mojego katalogu domowego. Jak mam to zrobić?
cp ./<the final 4 files> ~/
Odpowiedzi:
Można to łatwo zrobić za pomocą tablic bash / ksh93 / zsh:
a=(*)
cp -- "${a[@]: -4}" ~/
Działa to w przypadku wszystkich nie ukrytych nazw plików, nawet jeśli zawierają spacje, tabulatory, znaki nowej linii lub inne trudne znaki (zakładając, że w bieżącym katalogu są co najmniej 4 pliki nie ukryte bash
).
a=(*)
Spowoduje to utworzenie tablicy a
ze wszystkimi nazwami plików. Nazwy plików zwrócone przez bash są posortowane alfabetycznie. (Zakładam, że to właśnie rozumiesz przez „uporządkowane według nazwy pliku” ).
${a[@]: -4}
Zwraca ostatnie cztery elementy tablicy a
(pod warunkiem, że tablica zawiera co najmniej 4 elementy z bash
).
cp -- "${a[@]: -4}" ~/
Spowoduje to skopiowanie czterech ostatnich nazw plików do katalogu domowego.
Spowoduje to skopiowanie tylko czterech ostatnich plików do katalogu domowego, a jednocześnie dopisanie łańcucha a_
do nazwy skopiowanego pliku:
a=(*)
for fname in "${a[@]: -4}"; do cp -- "$fname" ~/a_"$fname"; done
Jeśli użyjemy a=(./some_dir/*)
zamiast a=(*)
, mamy problem z dołączeniem katalogu do nazwy pliku. Jednym z rozwiązań jest:
a=(./some_dir/*)
for f in "${a[@]: -4}"; do cp "$f" ~/a_"${f##*/}"; done
Innym rozwiązaniem jest użycie podpowłoki i cd
katalogu w podpowłoce:
(cd ./some_dir && a=(*) && for f in "${a[@]: -4}"; do cp -- "$f" ~/a_"$f"; done)
Po zakończeniu podpowłoki powłoka wraca do oryginalnego katalogu.
Pytanie dotyczy plików „uporządkowanych według nazwy pliku”. To zamówienie, zauważa Olivier Dulac w komentarzach, będzie się różnić w zależności od regionu. Jeśli ważne jest, aby ustalone wyniki były niezależne od ustawień komputera, najlepiej jest wyraźnie określić ustawienia narodowe, gdy tablica a
jest zdefiniowana. Na przykład:
LC_ALL=C a=(*)
Możesz dowiedzieć się, w którym aktualnie się znajdujesz, uruchamiając locale
polecenie.
Jeśli używasz zsh
, możesz zawrzeć w nawiasie ()
listę tak zwanych globalnych kwalifikatorów, które wybierają pożądane pliki. W twoim przypadku tak byłoby
cp *(On[1,4]) ~/
Tutaj On
sortuje nazwy plików alfabetycznie w odwrotnej kolejności i [1,4]
zajmuje tylko pierwsze 4 z nich.
Można zrobić to bardziej wytrzymałe wybierając tylko zwykłe pliki (z wyłączeniem katalogów, itp) z rur .
, a także poprzez dołączenie --
do cp
komendy w celu traktują pliki, których nazwy rozpoczynają się -
prawidłowo, więc:
cp -- *(.On[1,4]) ~
Dodaj D
kwalifikator, jeśli chcesz również uwzględnić ukryte pliki (pliki kropek):
cp -- *(D.On[1,4]) ~
*([-4,-1])
.
on
) jest zachowaniem domyślnym, więc nie trzeba tego określać, a -
przed liczbami nazwy plików liczą od dołu.
Oto rozwiązanie wykorzystujące niezwykle proste polecenia bash.
find . -maxdepth 1 -type f | sort | tail -n 4 | while read -r file; do cp "$file" ~/; done
Wyjaśnienie:
find . -maxdepth 1 -type f
Znajduje wszystkie pliki w bieżącym katalogu.
sort
Sortuje alfabetycznie.
tail -n 4
Pokaż tylko 4 ostatnie wiersze.
while read -r file; do cp "$file" ~/; done
Pętle w każdej linii wykonują polecenie kopiowania.
Dopóki uznasz, że sortowanie powłoki jest przyjemne, możesz po prostu:
set -- /path/to/source/dir/*
[ "$#" -le 4 ] || shift "$(($#-4))"
cp "$@" /path/to/target/dir
Jest to bardzo podobne do bash
oferowanego rozwiązania macierzy -specyficznego, ale powinno być przenośne dla dowolnej powłoki kompatybilnej z POSIX. Kilka uwag na temat obu metod:
Ważne jest, abyś poprowadził swoje cp
argumenty w / cp --
lub dostał jedną z .
kropek lub /
na początku każdego imienia. Jeśli tego nie zrobisz, ryzykujesz pierwszym -
myślnikiem cp
pierwszego argumentu, który może być interpretowany jako opcja i powodować niepowodzenie operacji lub w inny sposób generować niezamierzone wyniki.
set -- ./*
lub array=(./*)
.Ważne jest, aby przy użyciu obu metod upewnić się, że masz przynajmniej tyle elementów w tablicy arg, ile próbujesz usunąć - robię to tutaj z rozszerzeniem matematycznym. shift
Tylko się dzieje, jeśli istnieją co najmniej 4 pozycji w tablicy arg - i to tylko te pierwsze zmiany oddalony args które sprawiają, że nadwyżkę 4 ..
set 1 2 3 4 5
przypadku 1
odsunie to, ale w set 1 2 3
przypadku niczego nie zmieni.a=(1 2 3); echo "${a[@]: -4}"
drukuje pustą linię.Jeśli kopiujesz z jednego katalogu do drugiego, możesz użyć pax
:
set -- /path/to/source/dir/*
[ "$#" -le 4 ] || shift "$(($#-4))"
pax -rws '|.*/|new_prefix|' "$@" /path/to/target/dir
... który zastosowałby sed
podstawienie w stylu do wszystkich nazw plików podczas ich kopiowania.
Jeśli są tylko pliki, a ich nazwy nie zawierają białych znaków ani znaków nowej linii (a nie zmodyfikowałeś $IFS
) ani znaków globalnych (lub niektórych znaków niedrukowalnych z pewnymi implementacjami ls
) i nie zaczynaj od .
, możesz to zrobić :
cp -- $(ls | tail -n 4) ~/
a_
do WSZYSTKICH czterech plików? Próbowałem echo "a_$(ls | tail -n 4)"
, ale poprzedza tylko pierwszy plik.
ls
danych wyjściowych jest uważane za złą formę, ponieważ zwykle strasznie zawodzi w nazwach plików zawierających nie tylko spacje, ale także tabulatory, znaki nowej linii lub inne prawidłowe, ale „trudne” znaki.
To proste rozwiązanie bash bez kodu pętli. Skopiuj ostatnie 4 pliki z bieżącego katalogu do miejsca docelowego:
$ find . -maxdepth 1 -type f |tail -n -4|xargs cp -t "$destdir"
find
daje to pliki w pożądanej kolejności? Jak upewnić się, że pliki zawierające znaki białych znaków (w tym znaki nowej linii) w ich nazwach są obsługiwane poprawnie?
LC_ALL=C