ฉันจะจัดระเบียบไฟล์โดยใช้อักษรตัวแรกของชื่อไฟล์ลงในโฟลเดอร์ AZ ได้อย่างไร


15

ฉันกำลังมองหาวิธี (เทอร์มินัลเด่นกว่า) เพื่อจัดระเบียบแบบอักษรมากกว่า 1,000 แบบโดยใช้อักษรตัวแรก

โดยทั่วไปสร้างไดเรกทอรีA-Z, #จากนั้นย้ายไฟล์แบบอักษรไปยังไดเรกทอรีเหล่านั้นโดยยึดตามชื่อไฟล์ของอักขระตัวแรก แบบอักษรที่ขึ้นต้นด้วยหมายเลข [0-9] หรืออักขระพิเศษอื่น ๆ ที่จะย้ายไปยัง#ไดเรกทอรี


คุณต้องการให้ไดเรกทอรีทำแม้ว่าจะไม่มีไฟล์ที่ขึ้นต้นด้วยตัวอักษรนั้น
2560

@Arronical Nope เพียงแค่มีไฟล์
Parto

4
ฉันหวังว่าลิงก์นี้จะช่วยคุณstackoverflow.com/questions/1251938/…
Karthickeyan

คำตอบ:


13

ตัวเลือกหลามปลาย:

#!/usr/bin/env python3
import os
import sys
import shutil

def path(dr, f): return os.path.join(dr, f)

dr = sys.argv[1]
for f in os.listdir(dr):
    fsrc = path(dr, f)
    if os.path.isfile(fsrc):
        s = f[0]; target = path(dr, s.upper()) if s.isalpha() else path(dr, "#")
        if not os.path.exists(target):
            os.mkdir(target)
        shutil.move(fsrc, path(target, f))

วิธีใช้

  1. คัดลอกสคริปต์ไปยังไฟล์เปล่าบันทึกเป็น move_files.py
  2. เรียกใช้ด้วยไดเรกทอรีเป็นอาร์กิวเมนต์:

    python3 /path/to/move_files.py /path/to/files
    

สคริปต์จะสร้างเฉพาะไดเรกทอรี (ย่อย) (-ies) (ตัวพิมพ์ใหญ่) หากจำเป็นจริงๆ

คำอธิบาย

บท:

  • แสดงรายการไฟล์รับตัวอักษรตัวแรก (กำหนด sourcepath):

    for f in os.listdir(dr):
        s = f[0]; fsrc = path(dr, f)
  • ตรวจสอบว่ารายการเป็นไฟล์:

    if os.path.isfile(fsrc):
  • กำหนดโฟลเดอร์เป้าหมายหากอักขระตัวแรกคือ alpha หรือไม่:

    target = path(dr, s.upper()) if s.isalpha() else path(dr, "#")
  • ตรวจสอบว่าโฟลเดอร์นั้นมีอยู่แล้วหรือไม่สร้างขึ้นถ้าไม่:

    if not os.path.exists(target):
        os.mkdir(target)
  • ย้ายรายการไปยังโฟลเดอร์ที่เกี่ยวข้อง:

    shutil.move(fsrc, path(target, f))

เฮ้ยาโคบ มีการตรวจสอบอักษรตัวใหญ่ตัวพิมพ์ใหญ่หรือไม่?
Parto

@Parto อย่างแน่นอน! คุณต้องการมันในรูปแบบใด? (ฉันสามารถเช็คอินได้ในเวลาสอน
3.0

มันทำได้จริง การใช้งานที่สมบูรณ์แบบ
Parto

หลังจากพิจารณาแล้วฉันตัดสินใจที่จะตอบคำถามนี้ด้วยเหตุผลหลายประการ: 1) คำอธิบายเกี่ยวกับสิ่งที่เกิดขึ้น 2) คำตอบทำงานอย่างถูกต้องในการลองครั้งแรก
Parto

11

โค้ด golfed ยังสามารถอ่านได้ด้วยสองคำสั่งและสองนิพจน์ปกติ:

mkdir -p '#' {a..z}
prename -n 's|^[[:alpha:]]|\l$&/$&|; s|^[0-9]|#/$&|' [[:alnum:]]?*

หากคุณมีไฟล์จำนวนมากที่จะย้ายไฟล์มีจำนวนมากเกินกว่าที่จะใส่ลงในรายการอาร์กิวเมนต์กระบวนการ (ใช่มีข้อ จำกัด และอาจเป็นเพียงไม่กี่กิโลไบต์) คุณสามารถสร้างรายการไฟล์ด้วยคำสั่งและไพพ์อื่นprename, เช่น:

find -mindepth 1 -maxdepth 1 -name '[[:alnum:]]?*' -printf '%f\n' |
prename -n 's|^[[:alpha:]]|\l$&/$&|; s|^[0-9]|#/$&|'

นี่เป็นข้อดีเพิ่มเติมของการไม่พยายามย้ายชื่อไฟล์ตามตัวอักษร[[:alnum:]]?*หากไม่มีไฟล์ที่ตรงกับรูปแบบ glob findนอกจากนี้ยังช่วยให้เกณฑ์การจับคู่อื่น ๆ อีกมากมายกว่าเปลือก globbing ทางเลือกคือการตั้งค่าตัวเลือกเปลือกและปิดกระแสเข้ามาตรฐานของnullglob 1prename

ในทั้งสองกรณีลบ-nสวิตช์เพื่อย้ายไฟล์จริง ๆ และไม่เพียงแสดงว่าพวกเขาจะถูกย้ายอย่างไร

ภาคผนวก:คุณสามารถลบไดเรกทอรีว่างอีกครั้งด้วย:

rmdir --ignore-fail-on-non-empty '#' {a..z}

1 shopt -s nullglob; prename ... <&-


8

หากคุณไม่สนใจ zsh ฟังก์ชันและzmvคำสั่งสองสามคำสั่ง:

mmv() {echo mkdir -p "${2%/*}/"; echo mv -- "$1" "$2";}
autoload -U zmv
zmv -P mmv '([a-zA-Z])(*.ttf)' '${(UC)1}/$1$2'
zmv -P mmv '([!a-zA-Z])(*.ttf)' '#/$1$2'

mmvฟังก์ชั่นที่ทำให้ไดเรกทอรีและย้ายไฟล์ zmvจากนั้นให้การจับคู่รูปแบบและการทดแทน ขั้นแรกให้ย้ายชื่อไฟล์ที่เริ่มต้นด้วยตัวอักษรแล้วทุกอย่างอื่น:

$ zmv -P mmv '([a-zA-Z])(*.ttf)' '${(UC)1}/$1$2'
mkdir -p A/
mv -- abcd.ttf A/abcd.ttf
mkdir -p A/
mv -- ABCD.ttf A/ABCD.ttf
$ zmv -P mmv '([!a-zA-Z])(*.ttf)' '#/$1$2'
mkdir -p #/
mv -- 123.ttf #/123.ttf
mkdir -p #/
mv -- 七.ttf #/七.ttf

ทำงานอีกครั้งโดยไม่มีการกำหนดechoในmmvเพื่อดำเนินการย้ายจริง


8

ฉันไม่ได้คิดวิธีที่ดีที่จะทำให้ชื่อไดเรกทอรีเป็นตัวพิมพ์ใหญ่ (หรือย้ายไฟล์ด้วยตัวอักษรตัวพิมพ์ใหญ่) ถึงแม้ว่าคุณจะสามารถทำได้ในภายหลังด้วยrename...

mkdir {a..z} \#; for i in {a..z}; do for f in "$i"*; do if [[ -f "$f" ]]; then echo mv -v -- "$f" "$i"; fi; done; done; for g in [![:alpha:]]*; do if [[ -f "$g" ]]; then echo mv -v -- "$g" \#; fi; done

หรือมากกว่าอ่านได้ง่าย:

mkdir {a..z} \#; 
for i in {a..z}; do 
  for f in "$i"*; do
    if [[ -f "$f" ]]; then 
      echo mv -v -- "$f" "$i"; 
    fi 
  done
done
for g in [![:alpha:]]*; do 
  if [[ -f "$g" ]]; then 
    echo mv -v -- "$g" \#
  fi
done

ลบechoหลังจากการทดสอบเพื่อย้ายไฟล์จริง

และจากนั้น

rename -n 'y/[a-z]/[A-Z]/' *

ลบ-nถ้ามันดูดีหลังจากการทดสอบและเรียกใช้อีกครั้ง


2
คุณสามารถใช้if [[ -d "${i^}" ]]เพื่อสร้างiทุนตัวแปรและmkdir {A..Z}ในตอนเริ่มต้น
2560

ให้ฉันลองดูสิ
Parto

@Arronical ขอบคุณ! อย่างไรก็ตามฉันจะทิ้งไว้เนื่องจากคุณโพสต์ในแบบของคุณเอง
Zanna

@ ซานาฉันชอบที่เรามาจากทิศทางที่ต่างกันวนซ้ำผ่านตัวอักษรและใช้มันเป็นเกณฑ์การค้นหาที่ไม่เคยเกิดขึ้นกับฉัน ฉันแน่ใจว่ามีวิธีแก้ปัญหาที่ฉลาดและรวดเร็วในการค้นหา แต่ไม่สามารถไปรอบ ๆ มันได้!
2560

สวัสดี Zanna แบบนี้ไม่ได้ย้ายแบบอักษรที่ขึ้นต้นด้วยตัวอักษรตัวพิมพ์ใหญ่ ทำงานเป็นอย่างอื่นได้ดี
Parto

7

คำสั่งต่อไปภายในไดเรกทอรีที่มีแบบอักษรที่ควรจะทำงานถ้าคุณต้องการที่จะใช้จากนอกไดเรกทอรีจัดเก็บข้อมูลตัวอักษรที่เปลี่ยนแปลงไปfor f in ./* for f in /directory/containing/fonts/*นี่เป็นวิธีการทำงานของเชลล์ที่ทำงานช้ามากและไม่สามารถเรียกซ้ำได้ สิ่งนี้จะสร้างไดเรกทอรีเฉพาะถ้ามีไฟล์ที่ขึ้นต้นด้วยอักขระที่ตรงกัน

target=/directory/to/store/alphabet/dirs
mkdir "$target"
for f in ./* ; do 
  if [[ -f "$f" ]]; then 
    i=${f##*/}
    i=${i:0:1}
    dir=${i^}
    if [[ $dir != [A-Z] ]]; then 
      mkdir -p "${target}/#" && mv "$f" "${target}/#"
    else
      mkdir -p "${target}/$dir" && mv "$f" "${target}/$dir"
    fi
  fi
done

ในฐานะที่เป็นหนึ่งซับอีกครั้งจากภายในไดเรกทอรีที่เก็บแบบอักษร:

target=/directory/to/store/alphabet/dirs; mkdir "$target" && for f in ./* ; do if [[ -f "$f" ]]; then i=${f##*/}; i=${i:0:1} ; dir=${i^} ; if [[ $dir != [A-Z] ]]; then mkdir -p "${target}/#" && mv "$f" "${target}/#"; else mkdir -p "${target}/$dir" && mv "$f" "${target}/$dir" ; fi ; fi ; done

วิธีที่ใช้ find พร้อมกับการจัดการสตริงที่คล้ายกันโดยใช้การขยายพารามิเตอร์ bash ซึ่งจะเรียกซ้ำและควรค่อนข้างเร็วกว่าเวอร์ชันเชลล์บริสุทธิ์:

find . -type f -exec bash -c 'target=/directory/to/store/alphabet/dirs ; mkdir -p "$target"; f="{}" ; i="${f##*/}"; i="${i:0:1}"; i=${i^}; if [[ $i = [[:alpha:]] ]]; then mkdir -p "${target}/$i" && mv "$f" "${target}/$i"; else mkdir -p "${target}/#" && mv "$f" "${target}/#"; fi' \;

หรือมากกว่าอ่านได้ง่าย:

find . -type f -exec bash -c 'target=/directory/to/store/alphabet/dirs 
   mkdir -p "$target"
   f="{}"
   i="${f##*/}"
   i="${i:0:1}"
   i=${i^}
   if [[ $i = [[:alpha:]] ]]; then 
      mkdir -p "${target}/$i" && mv "$f" "${target}/$i"
   else
      mkdir -p "${target}/#" && mv "$f" "${target}/#"
   fi' \;

อันนี้ใช้ได้เช่นกัน แม้แต่ตัวอักษรตัวพิมพ์ใหญ่และตัวพิมพ์เล็ก
Parto

5

แมปชื่อไฟล์แต่ละรายการกับชื่อไดเรกทอรีโดยใช้trจากนั้นmkdirและmv:

find /src/dir -type f -print0 |
xargs -0 -I{} bash -c \
  'dir=/dest/$(basename "{}" | cut -c1 | tr -C "a-zA-Z\n" "#" | tr "a-z "A-Z"); mkdir -p $dir; mv "{}" $dir'

ฉันชอบสิ่งนี้มากและนี่คือสิ่งที่ฉันอยากจะใช้ในการค้นหา มีวิธีการเพียงสร้างชื่อไดเรกทอรีตัวพิมพ์ใหญ่และย้ายชื่อไฟล์ตัวพิมพ์เล็กลงไปหรือไม่?
2560

1
ฉันเพิ่มอีกอันtrเพื่อแปลงเป็นตัวพิมพ์ใหญ่
xn

ทำไมอ้อมผ่านxargsเพียงเพื่อโทรbashอีกครั้ง? มันจะไม่ง่ายและอ่านได้ง่ายกว่ามากในการไพพ์เอาท์พุทของfindการเป็น while-loop และreadมันมีการบันทึกโดยบันทึก?
David Foerster

เป็นคำถามที่ดี @DavidFoerster ฉันเดาว่าอคติกับการวนซ้ำในหนึ่ง liners และการตั้งค่าสำหรับวิธีการทำงานเพิ่มเติม แต่สคริปต์ทุบตีในสตริงนั้นก็ไม่ได้สง่างามเช่นกันดังนั้นฉันจึงบอกว่าwhileเวอร์ชั่นลูป ( bit.ly/2j2mhyb ) อาจจะดีกว่า
xn

โดยวิธีการที่คุณสามารถหลีกเลี่ยงการที่น่ารังเกียจ{}เปลี่ยนตัวถ้าคุณปล่อยให้xargsผนวกโต้แย้งแล้วหมายถึงภายในสคริปต์เปลือกเช่น:$1 xargs -0 -n1 -- bash -c 'dir=/dest/$(basename "$1" | ...); ...; mv "$1" "$dir"' _(ระวังสิ่งสุดท้าย_!)
David Foerster
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.