ฟังก์ชัน Bash เพื่อค้นหารูปแบบการจับคู่ไฟล์ใหม่ล่าสุด


141

ใน Bash ฉันต้องการสร้างฟังก์ชั่นที่คืนชื่อไฟล์ของไฟล์ใหม่ล่าสุดที่ตรงกับรูปแบบที่กำหนด ตัวอย่างเช่นฉันมีไดเรกทอรีของไฟล์เช่น:

Directory/
   a1.1_5_1
   a1.2_1_4
   b2.1_0
   b2.2_3_4
   b2.3_2_0

ฉันต้องการไฟล์ใหม่ล่าสุดที่ขึ้นต้นด้วย 'b2' ฉันจะทำเช่นนี้ในทุบตีได้อย่างไร ฉันต้องมีสิ่งนี้ใน~/.bash_profileสคริปต์ของฉัน


4
ดูsuperuser.com/questions/294161/…สำหรับคำแนะนำเพิ่มเติม การเรียงลำดับเป็นขั้นตอนสำคัญในการรับไฟล์ใหม่ล่าสุดของคุณ
Wolfgang Fahl

คำตอบ:


229

lsคำสั่งมีพารามิเตอร์-tเพื่อจัดเรียงตามเวลา จากนั้นคุณสามารถคว้าแรก (ใหม่ล่าสุด) head -1ด้วย

ls -t b2* | head -1

แต่ระวัง: ทำไมคุณไม่ควรแยกเอาท์พุทของ ls

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

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

นี่คือวิธีที่จะทำ "ถูกต้อง": ฉันจะหาไฟล์ (ล่าสุด, เก่าที่สุด, เก่าที่สุด) ในไดเรกทอรีได้อย่างไร?

unset -v latest
for file in "$dir"/*; do
  [[ $file -nt $latest ]] && latest=$file
done

8
หมายเหตุถึงผู้อื่น: หากคุณทำสิ่งนี้กับไดเรกทอรีคุณจะเพิ่มตัวเลือก -d ใน ls เช่นนี้ 'ls -td <pattern> | head -1 '
ken.ganong

5
แยก LSลิงค์กล่าวว่าจะไม่ทำเช่นนี้และแนะนำวิธีการในBashFAQ 99 ฉันกำลังมองหา 1-liner แทนที่จะเป็น bullet-bullet เพื่อรวมไว้ในสคริปต์ดังนั้นฉันจะดำเนินการแยกวิเคราะห์ต่อไปเช่น @lesmana อย่างไม่ปลอดภัย
บาร์

1
@Eponymous: หากคุณกำลังมองหาหนึ่งซับโดยไม่ต้องใช้เปราะบางls, printf "%s\n" b2* | head -1จะทำเพื่อคุณ
David Ongaro

2
@DavidOngaro คำถามไม่ได้บอกว่าชื่อไฟล์เป็นหมายเลขเวอร์ชั่น นี่เป็นเรื่องเกี่ยวกับเวลาการแก้ไข แม้จะมีข้อสันนิษฐานว่าชื่อไฟล์b2.10_5_2ก็ยังแก้ปัญหานี้ได้
Eponymous

1
ซับในของคุณให้คำตอบที่ถูกต้องแก่ฉัน แต่วิธีที่ "ถูก" คือให้ไฟล์ที่เก่าแก่ที่สุดแก่ฉัน มีความคิดอะไรไหม
NewNameStat

15

การรวมกันของfindและlsทำงานได้ดีสำหรับ

  • ชื่อไฟล์ที่ไม่มีบรรทัดใหม่
  • ไฟล์มีไม่มาก
  • ชื่อไฟล์ไม่ยาวมาก

การแก้ไขปัญหา:

find . -name "my-pattern" -print0 |
    xargs -r -0 ls -1 -t |
    head -1

มาทำลายมันกันเถอะ:

ด้วยfindเราสามารถจับคู่ไฟล์ที่น่าสนใจเช่นนี้ทั้งหมด:

find . -name "my-pattern" ...

จากนั้นใช้-print0เราสามารถส่งต่อชื่อไฟล์ทั้งหมดได้อย่างปลอดภัยlsเช่นนี้:

find . -name "my-pattern" -print0 | xargs -r -0 ls -1 -t

สามารถเพิ่มfindพารามิเตอร์และรูปแบบการค้นหาเพิ่มเติมได้ที่นี่

find . -name "my-pattern" ... -print0 | xargs -r -0 ls -1 -t

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

ในที่สุดก็head -1ทำให้เราเป็นไฟล์แรกในรายการเรียงลำดับ

หมายเหตุ: xargsใช้ระบบ จำกัด ขนาดของรายการอาร์กิวเมนต์ หากขนาดนี้เกินxargsจะเรียกlsหลายครั้ง สิ่งนี้จะทำลายการเรียงลำดับและอาจเป็นผลลัพธ์สุดท้าย วิ่ง

xargs  --show-limits

เพื่อตรวจสอบข้อ จำกัด ในระบบของคุณ

หมายเหตุ 2:ใช้find . -maxdepth 1 -name "my-pattern" -print0หากคุณไม่ต้องการค้นหาไฟล์ผ่านโฟลเดอร์ย่อย

หมายเหตุ 3:ในฐานะที่เป็นแหลมออกโดย @starfry - -rอาร์กิวเมนต์สำหรับการxargsป้องกันไม่ให้การเรียกร้องของถ้าไม่มีแฟ้มที่ถูกจับคู่โดยls -1 -t findขอบคุณสำหรับข้อเสนอแนะ


2
สิ่งนี้ดีกว่าโซลูชันที่ใช้ ls เนื่องจากทำงานกับไดเรกทอรีที่มีไฟล์จำนวนมากซึ่ง ls ฉายแสง
Marcin Zukowski

find . -name "my-pattern" ... -print0ให้ฉันfind: paths must precede expression: `...'
Jaakko

Oh! ...หมายถึง "พารามิเตอร์เพิ่มเติม" เพียงละเว้นมันหากคุณไม่ต้องการ
Boris Brodski

2
ฉันพบว่าสิ่งนี้สามารถส่งคืนไฟล์ที่ไม่ตรงกับรูปแบบหากไม่มีไฟล์ที่ตรงกับรูปแบบ มันเกิดขึ้นเพราะ find ส่งอะไรไปที่ xargs ซึ่งจะเรียกใช้ ls โดยไม่มีรายการไฟล์ทำให้มันทำงานบนไฟล์ทั้งหมดได้ ทางออกคือการเพิ่ม-rบรรทัดคำสั่ง xargs ซึ่งบอกให้ xargs ไม่เรียกใช้บรรทัดคำสั่งหากไม่ได้รับอะไรในอินพุตมาตรฐาน
starfry

@starfry ขอบคุณ! รับได้สวย. ฉันเพิ่ม-rไปยังคำตอบ
Boris Brodski

7

นี่เป็นการใช้งานที่เป็นไปได้ของฟังก์ชัน Bash ที่ต้องการ:

# Print the newest file, if any, matching the given pattern
# Example usage:
#   newest_matching_file 'b2*'
# WARNING: Files whose names begin with a dot will not be checked
function newest_matching_file
{
    # Use ${1-} instead of $1 in case 'nounset' is set
    local -r glob_pattern=${1-}

    if (( $# != 1 )) ; then
        echo 'usage: newest_matching_file GLOB_PATTERN' >&2
        return 1
    fi

    # To avoid printing garbage if no files match the pattern, set
    # 'nullglob' if necessary
    local -i need_to_unset_nullglob=0
    if [[ ":$BASHOPTS:" != *:nullglob:* ]] ; then
        shopt -s nullglob
        need_to_unset_nullglob=1
    fi

    newest_file=
    for file in $glob_pattern ; do
        [[ -z $newest_file || $file -nt $newest_file ]] \
            && newest_file=$file
    done

    # To avoid unexpected behaviour elsewhere, unset nullglob if it was
    # set by this function
    (( need_to_unset_nullglob )) && shopt -u nullglob

    # Use printf instead of echo in case the file name begins with '-'
    [[ -n $newest_file ]] && printf '%s\n' "$newest_file"

    return 0
}

มันใช้ Bash builtins เท่านั้นและควรจัดการไฟล์ที่มีชื่อมีการขึ้นบรรทัดใหม่หรืออักขระผิดปกติอื่น ๆ


1
คุณสามารถใช้nullglob_shopt=$(shopt -p nullglob)แล้วต่อมา$nullglobกลับมาใช้nullglobเหมือนเดิม
gniourf_gniourf

คำแนะนำโดย @gniourf_gniourf เพื่อใช้ $ (shopt -p nullglob) เป็นข้อเสนอที่ดี ฉันมักจะพยายามหลีกเลี่ยงการใช้การแทนที่คำสั่ง ( $()หรือ backticks) เพราะมันช้าโดยเฉพาะอย่างยิ่งภายใต้ Cygwin แม้ว่าคำสั่งจะใช้บิวอินเท่านั้น นอกจากนี้บริบทย่อยที่คำสั่งเรียกใช้อาจทำให้พวกเขาทำงานในลักษณะที่ไม่คาดคิด ฉันพยายามหลีกเลี่ยงการเก็บคำสั่งในตัวแปร (เช่นnullglob_shopt) เพราะสิ่งเลวร้ายอาจเกิดขึ้นได้หากคุณได้รับค่าของตัวแปรที่ไม่ถูกต้อง
pjh

ฉันขอขอบคุณที่ใส่ใจในรายละเอียดที่สามารถนำไปสู่ความล้มเหลวที่ไม่ชัดเจนเมื่อมองข้าม ขอบคุณ!
Ron Burk

ฉันรักที่คุณไปหาวิธีที่ไม่ซ้ำกันในการแก้ปัญหา! เป็นที่แน่นอนว่าใน Unix / Linux มีมากกว่าหนึ่งวิธีในการ 'สกิน the cat!' แม้ว่าจะใช้เวลามากขึ้น แต่ก็มีประโยชน์ในการแสดงแนวคิดของผู้คน มี +1!
Pryftan

3

ชื่อไฟล์ที่ผิดปกติ (เช่นไฟล์ที่มี\nตัวอักษรที่ถูกต้องสามารถสร้างความหายนะด้วยการแยกประเภทนี้นี่เป็นวิธีที่จะทำใน Perl:

perl -le '@sorted = map {$_->[0]} 
                    sort {$a->[1] <=> $b->[1]} 
                    map {[$_, -M $_]} 
                    @ARGV;
          print $sorted[0]
' b2*

นั่นคือการแปลงแบบชวาร์เชียนที่ใช้


1
ขอให้ schwartz อยู่กับคุณ!
Nathan Monteleone

คำตอบนี้อาจทำงานได้ แต่ฉันจะไม่เชื่อว่ามันให้เอกสารที่ดี
Wolfgang Fahl

1

คุณสามารถใช้statกับ glob ไฟล์และตกแต่งเรียงลำดับ undecorate กับเวลาไฟล์ที่เพิ่มที่ด้านหน้า:

$ stat -f "%m%t%N" b2* | sort -rn | head -1 | cut -f2-

Nope "stat: ไม่สามารถอ่านข้อมูลระบบไฟล์สำหรับ '% m% t% N': ไม่มีไฟล์หรือไดเรกทอรีดังกล่าว"
Ken Ingram

ฉันคิดว่านี่อาจเป็นของ Mac / FreeBSD เวอร์ชันstatหากฉันจำตัวเลือกต่าง ๆ ได้อย่างถูกต้อง เพื่อให้ได้ผลลัพธ์ที่คล้ายกันในแพลตฟอร์มอื่น ๆ คุณสามารถใช้stat -c $'%Y\t%n' b2* | sort -rn | head -n1 | cut -f2-
Jeffrey Cash

1

ฟังก์ชั่นเวทมนตร์มืดสำหรับผู้ที่ต้องการfind ... xargs ... head ...วิธีการแก้ปัญหาข้างต้น แต่ในรูปแบบฟังก์ชั่นที่ใช้งานง่ายคุณจึงไม่ต้องคิด:

#define the function
find_newest_file_matching_pattern_under_directory(){
    echo $(find $1 -name $2 -print0 | xargs -0 ls -1 -t | head -1)
}

#setup:
#mkdir /tmp/files_to_move
#cd /tmp/files_to_move
#touch file1.txt
#touch file2.txt

#invoke the function:
newest_file=$( find_newest_file_matching_pattern_under_directory /tmp/files_to_move/ bc* )
echo $newest_file

พิมพ์:

file2.txt

ซึ่งเป็น:

ชื่อไฟล์ที่มีการแก้ไขเวลาที่เก่าที่สุดของไฟล์ภายใต้ไดเรกทอรีที่กำหนดให้ตรงกับรูปแบบที่กำหนด


1

ใช้คำสั่ง find

สมมติว่าคุณใช้ Bash 4.2+ ให้ใช้-printf '%T+ %p\n'สำหรับค่าการประทับเวลาของไฟล์

find $DIR -type f -printf '%T+ %p\n' | sort -r | head -n 1 | cut -d' ' -f2

ตัวอย่าง:

find ~/Downloads -type f -printf '%T+ %p\n' | sort -r | head -n 1 | cut -d' ' -f2

สำหรับสคริปต์ที่มีประโยชน์มากขึ้นดูสคริปต์ค้นหาล่าสุดได้ที่นี่: https://github.com/l3x/helpers


เพื่อทำงานกับชื่อไฟล์ที่มีช่องว่างเปลี่ยน cut -d '' -f2,3,4,5,6,7,8,9 ...
valodzka

0

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

find . -cmin 1 -name "b2*"

คำสั่งนี้ค้นหาไฟล์ล่าสุดที่สร้างขึ้นหนึ่งนาทีที่ผ่านมาด้วยการค้นหาไวด์การ์ดใน "b2 *" หากคุณต้องการไฟล์จากสองวันที่ผ่านมาคุณจะดีขึ้นโดยใช้คำสั่งด้านล่าง:

find . -mtime 2 -name "b2*"

ส่วน "." แสดงถึงไดเรกทอรีปัจจุบัน หวังว่านี่จะช่วยได้


9
สิ่งนี้ไม่พบ "รูปแบบการจับคู่ไฟล์ใหม่ล่าสุด" ... มันแค่ค้นหารูปแบบการจับคู่ไฟล์ทั้งหมดที่สร้างขึ้นในนาทีที่ผ่านมาหรือแก้ไขเมื่อสองวันก่อน
GnP

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

"tweaking" ไม่ใช่คำตอบ มันเหมือนกับการโพสต์สิ่งนี้เป็นคำตอบ: "เพียงแค่ปรับแต่งคำสั่ง find และค้นหาคำตอบขึ้นอยู่กับสิ่งที่คุณต้องการจะทำ"
Kennet Celeste

ไม่แน่ใจเกี่ยวกับความคิดเห็นที่ไม่จำเป็น หากคุณรู้สึกว่าคำตอบของฉันไม่ได้รับการยืนยันโปรดระบุเหตุผลที่เหมาะสมว่าทำไมคำตอบของฉันจึงไม่สมเหตุสมผลกับตัวอย่าง หากไม่สามารถทำเช่นนั้นได้โปรดงดเว้นการแสดงความคิดเห็นเพิ่มเติม
Naufal

1
วิธีการแก้ปัญหาของคุณคุณจะต้องรู้ว่าเมื่อไฟล์ล่าสุดที่ถูกสร้างขึ้น นั่นไม่ได้อยู่ในคำถามดังนั้นไม่คำตอบของคุณไม่ได้ขึ้นอยู่กับคำถามที่ถูกวาง
Bloke Down The Pub
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.