En eski dosyaları taşımak için kabuk betiği?


14

Yalnızca en eski 20 dosyayı bir klasörden diğerine taşımak için nasıl komut dosyası yazabilirim? Bir klasördeki en eski dosyaları almanın bir yolu var mı?


Alt dizinler dahil mi, yoksa hariç mi? Ve özyinelemeli olarak mı yapılmalı (dizin ağacında)?
maxschlepzig

2
Birçok (en çok?) * Nix dosya sistemi oluşturma tarihini saklamaz, bu nedenle en eski dosyayı kesin olarak belirleyemezsiniz. Tipik olarak mevcut özellikler atime(son erişim), ctime(son izin değişikliği) ve mtime(son değiştirilme) ... ör. ls -tve Bul'un printf "%T" kullanımı mtime... O göre görünüyor bu bağlantı benim ki ext4bölünümlerdir yetenekli oluşturulma tarihi taşıma, ancak lsve findve stat... (henüz) uygun seçenekleri yok
Peter.O

Yanıtlar:


13

Çıktısını Ayrıştırma lsolan güvenilir değildir .

Bunun yerine, finddosyaları bulmak ve sortzaman damgasına göre sıralamak için kullanın. Örneğin:

while IFS= read -r -d $'\0' line ; do
    file="${line#* }"
    # do something with $file here
done < <(find . -maxdepth 1 -printf '%T@ %p\0' \
    2>/dev/null | sort -z -n)

Bütün bunlar ne yapıyor?

İlk olarak, findkomutlar geçerli dizindeki ( .) tüm dosyaları ve dizinleri bulur , ancak geçerli dizinin ( ) alt dizinlerinde bulmaz -maxdepth 1, sonra yazdırır:

  • Bir zaman damgası
  • Bir boşluk
  • Dosyanın göreceli yolu
  • NULL karakter

Zaman damgası önemlidir. %T@İçin biçim belirteci -printfayırır Tdosyası (mtime) ve "Son değişiklik zamanı" belirtir, @fraksiyonel saniye dahil "1970 yılından bu yana Saniye", gösterir.

Alan sadece keyfi bir sınırlayıcıdır. Dosyanın tam yolu, daha sonra başvurabilmemiz için NULL karakteri bir sonlandırıcıdır çünkü dosya adında geçersiz bir karakterdir ve bu nedenle dosya yolunun sonuna ulaştığımızdan emin olmanızı sağlar. dosya.

dahil ettim 2>/dev/null kullanıcı erişim izni yok dosyalar dışlanır, ama onlar hakkında hata mesajlar engellenmektedir dışlanmadan böylece.

findKomutun sonucu , geçerli dizindeki tüm dizinlerin listesidir. Liste, aşağıdakilere bildirilecek şekilde yönlendirilir sort:

  • -z NULL, satırsonu yerine satır sonlandırıcı karakteri olarak kabul edilir.
  • -n Sayısal olarak sırala

1970'den bu yana saniyeler her zaman yükseldiğinden, zaman damgası en küçük sayı olan dosyayı istiyoruz. İlk sonuçsort , en küçük numaralı zaman damgasını içeren satır olacaktır. Geriye kalan tek şey dosya adını çıkarmaktır.

Sonuçları find, sortboru hattı üzerinden geçirilir süreç ikamesi için whilestdin bir dosya sanki okumak nerede. whilesırayla readgirişi işlemek için çağırır .

Değişkeni bağlamında hiçbir şeye readayarlayamayız, IFSyani boşluk boşluk sınırlayıcı olarak yorumlanmayacaktır. kaçış genişlemesini devre dışı bırakan ve satır sonu sınırlayıcısını NULL yapan , boru hattımızın çıktısını eşleştiren readsöylenir .-r-d $'\0'findsort

Zaman damgası ve boşluğundan önce gelen en eski dosya yolunu temsil eden ilk veri parçası değişkene okunur line. Daha sonra, dizinin başlangıcından boşluk dahil olmak üzere ilk boşluğa kadar tüm karakterleri hiçbir şeyle değiştirmeyen ifade ile parametre ikamesi kullanılır #*. Bu, değişiklik zaman damgasını kaldırarak dosyanın tam yolunu bırakır.

Bu noktada dosya adı saklanır $fileve onunla istediğiniz her şeyi yapabilirsiniz. Eğer bir şey yapmak tamamladığınızda deyimi irade döngü ve komut sonraki öbek ve sonraki dosya adını çıkararak tekrar çalıştırılacaktır.$filewhileread

Daha basit bir yol yok mu?

Hayır. Daha basit yollar hatalı.

Eğer kullanıyorsanız ls -tve boru için headya tail(veya herhangi bir şey ) Eğer dosya adlarında satırsonu ile dosyalar üzerinde kırarım. Daha mv $(anything)sonra adında boşluk bulunan dosyalar kırılmaya neden olur. Daha mv "$(anything)"sonra adında sondaki yeni satırları olan dosyalar kırılmaya neden olur. Eğer readolmadan -d $'\0'o zaman adlarında boşluk ile dosyalar üzerinde kırarım.

Belki belirli durumlarda, daha basit bir yolun yeterli olduğundan emin olabilirsiniz, ancak bunu önlemek için komut dosyalarına asla böyle varsayımlar yazmamalısınız.

Çözüm

#!/usr/bin/env bash

# move to the first argument
dest="$1"

# move from the second argument or .
source="${2-.}"

# move the file count in the third argument or 20
limit="${3-20}"

while IFS= read -r -d $'\0' line ; do
    file="${line#* }"
    echo mv "$file" "$dest"
    let limit-=1
    [[ $limit -le 0 ]] && break
done < <(find "$source" -maxdepth 1 -printf '%T@ %p\0' \
    2>/dev/null | sort -z -n)

Şöyle arayın:

move-oldest /mnt/backup/ /var/log/foo/ 20

En eski 20 dosyayı konumundan konumuna taşımak /var/log/foo/için /mnt/backup/.

Dosya ve dizinler eklediğimi unutmayın. Dosyalar için yalnızca eklemek -type fiçin findçağırma.

Teşekkürler

Bu cevabı geliştirdikleri için enzotib ve Павел Танков'a teşekkürler .


Sıralama kullanılmamalıdır -n. En azından versiyonumda, ondalık sayıları doğru sıralamıyor. Tarihteki noktayı kaldırmanız veya kullanmanız -printf '%TY-%Tm-%TdT%TH:%TM:%TS %p\0' | sort -rz, ISO tarihleri ​​veya başka bir şey kullanmanız gerekir.
l0b0

@ l0b0: Bu sınırlama benim için biliniyor. Bu ayrıntı düzeyini gerektirmemenin yeterli olduğunu düşünüyorum (yani, ötesinde sıralama .sizin için önemsiz olmalı.) Söylemek daha açık olurdu sort -z -n -t. -k1.
Sorpigal

@ l0b0: Çözümünüz ne olursa olsun aynı hatayı %TSgösterir : ayrıca formda olabilecek bir "kesirli kısım" da gösterir 00.0000000000, böylece ayrıntı düzeyini de kaybedersiniz. Son GNU , bu tür kayan noktayı beklendiği gibi işleyecek bir "sürüm sıralaması" sortkullanarak bu sorunu çözebilir -V.
Sorpigal

Hayır, çünkü sayısal bir sıralama yerine "YYYY-AA-GGTss: mm: ss" dizesinde sıralama yapıyorum . Dize sıralaması ondalık
sayılarla

@ l0b0: %T@O zaman sıfır dolgulu olduğu için bir dize sıralaması da işe yarayacaktır.
Sorpigal

4

Maçları tarihe göre (ilk önce en eski) sıralamak için Om glob niteleyicisini ve [1,20]yalnızca ilk 20 maçı korumak için niteleyiciyi kullanabileceğiniz zsh'da en kolay olanıdır :

mv -- *(Om[1,20]) target/

DNokta dosyalarını da eklemek istiyorsanız niteleyiciyi ekleyin . Ekle.Dizinlerle değil, yalnızca normal dosyalarla eşleşmek istiyorsanız .

Zsh'niz yoksa, burada bir Perl tek astarlı (80 karakterden daha az bir sürede yapabilirsiniz, ancak netlikle daha fazla masrafla yapabilirsiniz):

perl -e '@files = sort {-M $b <=> -M $a} glob("*"); foreach (@files[0..1]) {rename $_, "target/$_" or die "$_: $!"}'

Yalnızca POSIX araçlarıyla veya bash veya ksh ile dosyaları tarihe göre sıralamak acı vericidir. Bunu kolayca yapabilirsiniz ls, ancak çıktısını ayrıştırmak lssorunlu olduğundan, bu yalnızca dosya adları yalnızca satırsonu dışında yazdırılabilir karakterler içeriyorsa çalışır.

ls -tr | head -n 20 | while IFS= read -r file; do mv -- "$file" target/; done

4

ls -tÇıkışı tailveya ile birleştirin head.

Yalnızca tüm dosya adları yalnızca boşluk dışında yazdırılabilir karakterler içeriyorsa çalışan basit örnek ve \[*?:

 mv $(ls -1tr | head -20) other_folder

1
-A seçeneğini ls -1Atr
ls'ye ekleyin

1
-1, tehlikeli. İşte bana bir örnek zanaat alalım: touch $'foo\n*'. Orada oturan dosya ile mv "$ (ls)" yürütürseniz ne olur?
Sorpigal

1
@Sorpigal Cidden mi? "Özellikle işe yaramayacağını söylediğin bir örnek bulmama izin ver. Hey bak, işe yaramıyor" demek biraz zayıf
Michael Mrozek

1
@Sorpigal Kötü bir fikir değil, vakaların% 99'unda çalışıyor. Bu sorunun cevabı "Eğer normal adlara sahip dosyalarınız varsa, bu işe yarar. Dosya adlarına satırsonu ekleyen çılgın bir insansanız," olmaz. Bu tamamen doğru
Michael Mrozek

1
@MichaelMrozek: Kötü bir fikir ve kötü çünkü bazen başarısız oluyor. Bazen başarısız olan ve olmayanı yapma seçeneğiniz varsa, olmayan seçeneği (ve kötü olanı) almalısınız. İstediğinizi etkileşimli olarak yapın, ancak bir komut dosyasında ve tavsiye verirken doğru şekilde yapın.
Sorpigal

3

Bunun için GNU bulmayı kullanabilirsiniz:

find -maxdepth 1 -type f -printf '%T@ %p\n' \
  | sort -k1,1 -g | head -20 | sed 's/^[0-9.]\+ //' \
  | xargs echo mv -t dest_dir

Bul, değişiklik zamanını (1970'den saniye cinsinden) ve geçerli dizinin her dosyasının adını yazdırdığında, çıktı ilk alana göre sıralanır, en eski 20'si filtrelenir ve konumuna taşınır dest_dir. echoKomut satırını test ettiyseniz kaldırın .


2

Hiç kimse dosya adında gömülü yeni satır karakterleri (herhangi bir şey gömülü) için hitap eden bir bash örneği yayınlamadı, bu yüzden burada bir tane var. En eski 3 (mdate) normal dosyayı taşır

move=3
find . -maxdepth 1 -type f -name '*' \
 -printf "%T@\t%p\0" |sort -znk1 | { 
  while IFS= read -d $'\0' -r file; do
      printf "%s\0" "${file#*$'\t'}"
      ((--move==0)) && break
  done } |xargs -0 mv -t dest

Bu test verisi snippetidir

# make test files with names containing \n, \t and "  "
rm -f '('?[1-4]'  |?)'
for f in $'(\n'{1..4}$'  |\t)' ;do sleep .1; echo >"$f" ;done
touch -d "1970-01-01" $'(\n4  |\t)'
ls -ltr '('?[1-4]'  |'?')'; echo
mkdir -p dest

İşte kontrol sonuçları snippet'i

  ls -ltr '('?[1-4]'  |'?')'
  ls -ltr   dest/*

+1, sadece benimkinden önce yararlı yanıt (ve test verilerinin olması her zaman iyidir.)
Sorpigal

0

GNU ile yapmak en kolayı find. Linux DVR'ımda her gün bir günden eski video gözetim sistemimdeki kayıtları silmek için kullanıyorum.

Sözdizimi şöyledir:

find /path/to/files/* -mtime +number_of_days -exec mv {} /path/to/folder \;

Bunu hatırla find bir günü icra tarihinden itibaren 24 saat olarak tanımladığını . Bu nedenle en geç 23:00 de değiştirilen dosyalar 01: 00'de silinmez.

Hatta birleştirebilirsiniz findile cronsilme root olarak aşağıdaki komutu çalıştırarak otomatik planlanmış olabilir, böylece:

crontab -e << EOF
@daily /usr/bin/find /path/to/files/* -mtime +number_of_days -exec mv {} /path/to/folder \;
EOF

findKılavuz sayfasına başvurarak her zaman daha fazla bilgi edinebilirsiniz :

man find

0

diğer cevaplar benim ve soruların amacına uymadığından, bu kabuk CentOS 7'de test edilmiştir:

oldestDir=$(find /yourPath/* -maxdepth 0 -type d -printf '%T+ %p\n' | sort | head -n 1 | tr -s ' ' | cut -d ' ' -f 2)
echo "$oldestDir"
rm -rf "$oldestDir"
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.