รักษาโครงสร้างไดเรกทอรีเมื่อย้ายไฟล์โดยใช้การค้นหา


22

ฉันสร้างสคริปต์ต่อไปนี้ซึ่งย้ายไฟล์วันเก่าตามที่กำหนดจากไดเรกทอรีต้นทางไปยังไดเรกทอรีปลายทาง มันทำงานได้อย่างสมบูรณ์แบบ

#!/bin/bash

echo "Enter Your Source Directory"
read soure

echo "Enter Your Destination Directory"
read destination 

echo "Enter Days"
read days



 find "$soure" -type f -mtime "-$days" -exec mv {} "$destination" \;

  echo "Files which were $days Days old moved from $soure to $destination"

สคริปต์นี้ย้ายไฟล์ได้อย่างยอดเยี่ยมนอกจากนี้ยังย้ายไฟล์ของไดเรกทอรีย่อยต้นทาง แต่ไม่สร้างไดเรกทอรีย่อยลงในไดเรกทอรีปลายทาง ฉันต้องการใช้คุณสมบัติเพิ่มเติมนี้ในนั้น

ด้วยตัวอย่าง

/home/ketan     : source directory

/home/ketan/hex : source subdirectory

/home/maxi      : destination directory

เมื่อฉันเรียกใช้สคริปต์นี้มันจะย้ายไฟล์ของ hex ในไดเรกทอรี maxi ด้วย แต่ฉันต้องการ hex ที่เหมือนกันควรสร้างไว้ในไดเรกทอรี maxi และย้ายไฟล์ใน hex เดียวกันนั้น

คำตอบ:


13

แทนการทำงานคุณจะต้องแตกต่างกันไปไดเรกทอรีเป้าหมายขึ้นอยู่กับเส้นทางที่ผลิตโดยmv /home/ketan/hex/foo /home/maxi นี้เป็นง่ายขึ้นถ้าคุณเปลี่ยนไดเรกทอรีแหล่งที่มาครั้งแรกและการทำงานfind ตอนนี้คุณสามารถเพียงย่อหน้าไดเรกทอรีปลายทางแต่ละรายการที่ผลิตโดยfind . findคุณจะต้องเรียกใช้เชลล์ในfind … -execคำสั่งเพื่อดำเนินการเชื่อมต่อและสร้างไดเรกทอรีเป้าหมายหากจำเป็น

destination=$(cd -- "$destination" && pwd) # make it an absolute path
cd -- "$source" &&
find . -type f -mtime "-$days" -exec sh -c '
  mkdir -p "$0/${1%/*}"
  mv "$1" "$0/$1"
' "$destination" {} \;

โปรดทราบว่าเพื่อหลีกเลี่ยงปัญหาข้อความถ้า$destinationมีอักขระพิเศษคุณไม่สามารถแทนที่มันในสคริปต์เชลล์ คุณสามารถส่งออกไปยังสภาพแวดล้อมเพื่อให้ถึงเปลือกด้านในหรือคุณสามารถผ่านมันเป็นอาร์กิวเมนต์ (นั่นคือสิ่งที่ฉันทำ) คุณอาจประหยัดเวลาในการดำเนินการเล็กน้อยโดยการจัดกลุ่มการshโทร:

destination=$(cd -- "$destination" && pwd) # make it an absolute path
cd -- "$source" &&
find . -type f -mtime "-$days" -exec sh -c '
  for x do
    mkdir -p "$0/${x%/*}"
    mv "$x" "$0/$x"
  done
' "$destination" {} +

อีกทางเลือกหนึ่งใน zsh, คุณสามารถใช้zmvฟังก์ชั่นและ.และm glob บ่นเพียงตรงกับไฟล์ปกติในช่วงวันที่ที่เหมาะสม คุณจะต้องผ่านmvฟังก์ชันทางเลือกที่สร้างไดเรกทอรีเป้าหมายก่อนหากจำเป็น

autoload -U zmv
mkdir_mv () {
  mkdir -p -- $3:h
  mv -- $2 $3
}
zmv -Qw -p mkdir_mv $source/'**/*(.m-'$days')' '$destination/$1$2'

for x doคุณพลาดไป;แล้ว :) นอกจากนี้ฉันไม่รู้ว่าสิ่งที่คุณต้องการบรรลุด้วย$0แต่ฉันค่อนข้างมั่นใจว่ามันจะเป็นsh:)
MichałGórny

@ MichałGórnyในfor x; doทางเทคนิคไม่ใช่ POSIX-compliant (ตรวจสอบไวยากรณ์ ), แต่กระสุนสมัยใหม่อนุญาตทั้งสองfor x doและfor x; do; กระสุน Bourne เก่าบางตัวไม่ยอมfor x; doตาย บนเปลือกหอยที่ทันสมัยด้วยsh -c '…' arg0 arg1 arg2 arg3, arg0กลายเป็น$0, arg1กลายเป็น$1ฯลฯ หากคุณต้องการ$0ที่จะเป็นคุณจะต้องเขียนsh sh -c '…' sh arg1 arg2 arg3อีกครั้งเชลล์ Bourne บางตัวทำงานแตกต่างกัน แต่POSIXระบุสิ่งนี้
Gilles 'หยุดความชั่วร้าย'

pushdดูเหมือนจะเป็นทางเลือกที่ดีกว่าcdเพราะมันไม่ค่อยก้าวก่ายสภาพแวดล้อมในปัจจุบัน
jpmc26

16

ฉันรู้ว่าfindได้รับการระบุ rsyncแต่เสียงนี้เหมือนงานสำหรับ

ฉันมักจะใช้สิ่งต่อไปนี้:

rsync -axuv --delete-after --progress Source/ Target/

นี่เป็นตัวอย่างที่ดีถ้าคุณต้องการย้ายไฟล์ประเภทไฟล์เฉพาะ ( ตัวอย่าง ):

rsync -rv --include '*/' --include '*.js' --exclude '*' --prune-empty-dirs Source/ Target/

สะอาดกว่าโซลูชันอื่น ๆ :)
Guillaume

2
ฉันพบว่า--remove-source-filesมีประโยชน์ซึ่งทำให้ไฟล์ถูกย้ายอย่างมีประสิทธิภาพแทนที่จะถูกคัดลอก นี่คือการใช้ rsync ที่ยอดเยี่ยม
ทอม

3

คุณสามารถทำได้โดยใช้อินสแตนซ์การค้นหาสองรายการ (1)

cpio มีอยู่เสมอ (1)

(cd "$soure" && find  | cpio -pdVmu "$destination")

ตรวจสอบข้อโต้แย้งสำหรับ cpio สิ่งที่ฉันให้


1
สิ่งนี้จะทำลายชื่อไฟล์ใด ๆ ที่มีช่องว่าง
Chris Down

1
สิ่งนี้จะคัดลอกไฟล์แทนที่จะย้ายพวกมัน
Gilles 'SO- หยุดความชั่วร้าย'

อาจไม่ใช่คำตอบที่สมบูรณ์แบบ แต่ช่วยให้ฉันย้ายไฟล์ที่เก็บรักษาพา ธ ในลักษณะตรงไปตรงมามากขึ้นหรือน้อยลง (ฉันมีพื้นที่เพียงพอที่จะคัดลอกไฟล์แล้วลบ) Upvoted
AhHatem

3

มันไม่ได้มีประสิทธิภาพ แต่รหัสนั้นง่ายต่อการอ่านและทำความเข้าใจในความคิดของฉันถ้าคุณเพียงแค่คัดลอกไฟล์แล้วลบในภายหลัง

find /original/file/path/* -mtime +7 -exec cp {} /new/file/path/ \;
find /original/file/path/* -mtime +7 -exec rm -rf {} \;

ประกาศ: ข้อบกพร่องที่ค้นพบโดย @MV สำหรับการทำงานอัตโนมัติ:

การใช้สองการปฏิบัติการแยกกันนั้นมีความเสี่ยง หากบางไฟล์เก่ากว่า 7 วันในขณะที่ทำการคัดลอกไฟล์จะไม่ถูกคัดลอก แต่จะถูกลบโดยการลบ สำหรับสิ่งที่ทำด้วยตนเองในครั้งนี้อาจไม่เป็นปัญหา แต่สำหรับสคริปต์อัตโนมัติอาจทำให้ข้อมูลสูญหายได้


2
การใช้สองการปฏิบัติการแยกกันนั้นมีความเสี่ยง หากบางไฟล์เก่ากว่า 7 วันในขณะที่ทำการคัดลอกไฟล์จะไม่ถูกคัดลอก แต่จะถูกลบโดยการลบ สำหรับสิ่งที่ทำด้วยตนเองในครั้งนี้อาจไม่เป็นปัญหา แต่สำหรับสคริปต์อัตโนมัติอาจทำให้ข้อมูลสูญหายได้
MV

2
ทางออกที่ง่ายสำหรับข้อบกพร่องนี้คือการเรียกใช้ค้นหาหนึ่งครั้งบันทึกรายการเป็นไฟล์ข้อความแล้วใช้ xargs สองครั้งเพื่อทำสำเนาและลบ
David M. Perlman

1

คุณสามารถทำได้โดยต่อท้ายพา ธ สัมบูรณ์ของไฟล์ที่ส่งคืนโดยfindไปยังพา ธ ปลายทาง

find "$soure" -type f -mtime "-$days" -print0 | xargs -0 -I {} sh -c '
    file="{}"
    destination="'"$destination"'"
    mkdir -p "$destination/${file%/*}"
    mv "$file" "$destination/$file"'

ตอนนี้คริสย้ายบ้านไปที่แมกซี่
Ketan Patel

@ K.KPatel ไม่มันไม่ได้ มันทำให้โครงสร้างไดเรกทอรีของคุณ
Chris Down

ตัวแบ่งนี้หาก$destinationมีอักขระพิเศษเนื่องจากผ่านการขยายในเปลือกด้านใน บางทีคุณอาจหมายถึงdestination='\'"$destination"\''? 'ที่ยังคงอยู่ในการแบ่ง นอกจากนี้จะสร้างไฟล์เช่นแทน/home/maxi/home/ketan/hex/foo /home/maxi/hex/foo
Gilles 'SO- หยุดความชั่วร้าย'

0

ดีกว่า (เร็วที่สุด & ไม่ต้องใช้พื้นที่เก็บข้อมูลด้วยการทำสำเนาแทนที่จะย้าย) และจะไม่ได้รับผลกระทบจากชื่อไฟล์หากมีอักขระพิเศษในชื่อ:

export destination
find "$soure" -type f "-$days" -print0 | xargs -0 -n 10 bash -c '
for file in "$@"; do
  echo -n "Moving $file to $destination/"`dirname "$file"`" ... "
  mkdir -p "$destination"/`dirname "$file"`
  \mv -f "$file" "$destination"/`dirname "$file"`/ || echo " fail !" && echo "done."
done'

หรือเร็วกว่านั้นการย้ายไฟล์หลาย ๆ ไฟล์ในเวลาเดียวกันสำหรับ multi-CPU โดยใช้คำสั่ง "parallel":

echo "Move oldest $days files from $soure to $destination in parallel (each 10 files by "`parallel --number-of-cores`" jobs):"
function move_files {
  for file in "$@"; do
    echo -n "Moving $file to $destination/"`dirname "$file"`" ... "
    mkdir -p "$destination"/`dirname "$file"`
    \mv -f "$file" "$destination"/`dirname "$file"`/ || echo " fail !" && echo "done."
  done
}
export -f move_files
export destination
find "$soure" -type f "-$days" -print0 | parallel -0 -n 10 move_files

PS: คุณมีตัวพิมพ์ผิด "soure" ควรเป็น "source" ฉันเก็บชื่อตัวแปร


0

นี่คือความหรูหราน้อยกว่า แต่ง่ายถ้าจำนวน / ขนาดของไฟล์ไม่ดีเกินไป

ซิปไฟล์ของคุณไว้ในที่zipเก็บถาวรแล้วเปิดเครื่องรูดที่ปลายทางโดยไม่มี-jตัวเลือก โดยค่าเริ่มต้น zip จะสร้างโครงสร้างไดเรกทอรีสัมพันธ์


0

ลองด้วยวิธีนี้:

IFS=$'\n'
for f in `find "$soure" -type f -mtime "-$days"`;
do
  mkdir -p "$destination"/`dirname $f`;
  mv $f "$destination"/`dirname $f`;
done

0

เนื่องจากดูเหมือนจะไม่มีวิธีแก้ปัญหาที่ง่ายมากและฉันต้องการมันบ่อยมากฉันจึงสร้างยูทิลิตี้แบบโอเพ่นซอร์สสำหรับ linux (ต้องการ python): https://github.com/benapetr/smv

มีหลายวิธีที่คุณสามารถใช้เพื่อให้ได้สิ่งที่คุณต้องการ แต่อาจเป็นวิธีที่ง่ายที่สุด:

 # -vf = verbose + force (doesn't stop on errors)
smv -vf `find some_folder -type f -mtime "-$days"` target_folder

คุณสามารถเรียกใช้เพิ่มเติมในโหมดแห้งเพื่อที่จะไม่ทำอะไรนอกจากพิมพ์สิ่งที่จะทำ

smv -rvf `find some_folder -type f -mtime "-$days"` target_folder

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

find "$soure" -type f -mtime "-$days" -exec smv {} "$destination" \;
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.