פרל, פייתון ורובי מסדרות קבצים בתיקיות

09/06/2016

פוסט זה כולל טיפ קצר לעבודה יעילה עם Python. אם אתם רוצים ללמוד פייתון יותר לעומק אני ממליץ על קורס Python כאן באתר.
הקורס כולל עשרות שיעורי וידאו והמון תרגול מעשי וילמד אתכם Python בצורה מקצועית מההתחלה ועד הנושאים המתקדמים.

השוואה בין שפות תכנות דומות היא דרך טובה להבין טוב יותר את ההבדלים וגם את נקודות ההשקה וההשפעה ההדדית בין השפות.

לפינה הפעם בחרתי לכתוב תוכנית הממיינת קבצים לתיקיות לפי שמם, כך שכל הקבצים שמתחילים באותה אות יישמרו יחד בתיקיה. אותה התוכנית, 3 שפות, ובסוף נחשוב מה יצא הכי נעים לעין.

1. פרל: הכי קרוב ליוניקס

יותר מכל פרל מזכירה לי את bash על סטרואידים. שילוב של סימני ה-$, שמות הפונקציות והבדיקה האם משהו הוא קובץ עם -f.

use v5.14;
use strict;
use warnings;
use File::Spec;

my $folder = $ARGV[0] or die "Usage: $0 <folder>";

chdir($folder) or die "Failed: chdir($folder)";
my @files = glob("*");

foreach my $file (@files) {
  next unless -f $file;

  my $folder = substr($file, 0, 1);
  mkdir $folder;

  rename $file, File::Spec->catfile($folder, $file);
}

הפרמטר היחיד של התוכנית הוא שם של תיקיה בה התוכנית תחפש קבצים לארגון. קריאה ל mkdir כשהתיקיה כבר קיימת לא תשבור שום דבר ולכן אין צורך לטפל במפורש במקרה זה.

מצד שני אין טיפול בשגיאות כך שאם קיים קובץ ששמו הוא אות אחת דברים עלולים להשבר בדרכים לא צפויות.

2. פייתון: לאט אבל בטוח

בספירת השורות אין הבדל בין פרל לפייתון, אבל גירסת הפייתון שלי מתאפיינת בשורות ארוכות יותר מאשר גירסת פרל המקבילה. זה לא תמיד לרעה שכן פייתון מכריחה אותי לטפל ביותר מקרים שפרל מאפשרת להדחיק:

import argparse
import os
import os.path

parser = argparse.ArgumentParser()
parser.add_argument('folder', nargs=1)
args = parser.parse_args()
folder = args.folder[0]

files = os.listdir(folder)

for file in files:
    if not os.path.isfile(file): continue

    folder = file[0]
    os.makedirs(folder, exist_ok=True)

    os.rename(file, os.path.join(folder, file))

בפרט שימו לב לפקודת יצירת התיקיות. פייתון דורשת שתציינו במפורש מה לעשות אם התיקיה כבר קיימת, וממילא תציץ שגיאות אחרות, למשל אם ניסיתם ליצור תיקיה בשם של קובץ.

דוגמא נוספת היא פענוח הארגומנטים. פייתון הצליחה לסבך את השימוש ב sys.argv כך שקל יותר להשתמש ב argparse כדי לקחת את שם התיקיה משורת הפקודה. הכתיב ארוך יותר אבל גם התוצאה תחזיק מעמד טוב יותר כשנצטרך להוסיף פרמטרים.

ניגוד נוסף בין פייתון לפרל נמצא בשליפת האות הראשונה משם הקובץ. פייתון משתמשת באותו האופרטור (סוגריים מרובעים) כדי לקבל את האיבר הראשון ברשימה וגם כדי לקבל את האות הראשונה במחרוזת. בפרל אלו שתי פעולות שונות ולכל אחת דרך הכתיבה שלה. יתרון הקריאות כאן לדעתי בצד של פרל שכן שם לא צריך להתעמק בקוד בשביל לדעת מה סוגו של כל משתנה.

3. רובי: שילוב מנצח

המנצחת הגדולה בהשוואה הפעם היא רובי המציעה שילוב בין המיקוד של פרל לקריאות של פייתון:

require 'fileutils'

folder = ARGV[0] or raise "Usage: $0 <folder>"

Dir.chdir(folder)
files = Dir.glob("*")

files.each do |fname|
  next unless File.file?(fname)

  folder = fname[0]

  FileUtils.mkdir_p folder
  FileUtils.mv fname, File.join(folder, fname)
end

רובי לא כעסה כשלקחתי את פרמטר שורת הפקודה בצורה חובבנית, ואפילו אפשרה לי להשתמש באותו המבנה ובאותו $0 של פרל. פקודות יוניקס נשארות עם השם הטבעי שלהן והפקודה mkdir_p היא בדיוק מה שחיפשתי לתרגיל הזה: יצירת תיקיה או התעלמות אם כבר יש תיקיה כזו, ושגיאה אם יש קובץ בשם זה או שלא ניתן ליצור שם תיקיה בגלל בעיות אחרות.

4. באש: קבוצת הביקורת

לא באמת במשחק הזה כי היא הרבה יותר מוכוונת לעבודה עם קבצים ותיקיות (וחסרה הרבה מהיכולות שיש לרובי, פרל ופייתון), אבל עדיין נחמד לראות כקבוצת ביקורת:

#!/bin/bash

cd $1

for f in *
do
  [[ -f $f ]] || continue

  mkdir -p ${f:0:1}
  mv "$f" "${f:0:1}/$f"
done

ספרו בתגובות איזה מההצעות אתם הכי אהבתם, או אם יש לכם רעיונות אחרים לשיפור התוכנית.