שרשור פעולות בלינוקס

הפילוסופיה של יוניקס (ושל לינוקס) היא שכל כלי יעשה פעולה אחת, ויעשה אותה כמו שצריך – והמשתמש יחבר את הכלים כדי להשיג את המטרה שלו.
הפילוסופיה הזו שונה מזו של מערכות הפעלה יותר "ידידותיות", שנותנות למשתמש כלים שעושים את מה שהמתכנתים חשבו שהמשתמש ירצה לעשות – לא פחות ולא יותר.
מצד אחד אותם משתמשים ישיגו את המטרה שלהם – אם המתכנתים חשבו שהיא מטרה ראויה – יותר בקלות, מצד שני, אם המתכנתים לא חשבו שהמטרה הזו מספיק חשובה או אם הם בכלל לא חשבו על הצורך המסויים המשתמש פשוט לא יוכל להשיג את המטרה בעזרת אותם כלים ידידותיים.
אז אחרי ההקדמה הזו, הנה דוגמא עם בעיה אמיתית:
לFireStats יש כרגע 16 תרגומים, והמתרגמים אחראים לעדכן עצמאית את התרגום שלהם ברגע שאני מודיע על הזמינות של גרסא חדשה במערכת הגרסאות של הקוד (Subversion).
לפעמים המתרגמים לוקחים את הזמן, ומעדכנים רק אחרי כמה שבועות, ובדרך כלל מודיעים לי שהם עדכנו, אבל לא תמיד.
עכשיו, איך אני יכול לדעת מי עדיין לא עדכן את התרגום?
אני יכול לתחזק רשימה, ולמחוק מהרשימה כל אחד שהודיע לי שהוא תרגם, אבל אני עצלן מדי ובטח אשכח לתחזק אותה, מה שיהפוך אותה ללא שימושית מהר מאוד.
דרך נוספת היא להסתכל בקבצי התרגום, ולחפש תרגומים שלא מכילים מילה כלשהי שמופיעה רק בגרסא החדשה, אבל איך עושים את זה?
כלים לחיפוש בקבצים בדרך כלל ימצאו קבצים שמכילים משהו, לא קבצים שלא מכילים.
יש הרבה דרכים להשיג את המטרה הזו, רובן מערבות מציאה של כלי יעודי לא ידוע או כתיבת תוכנית – אבל לאור ההקדמה – ברור שאני אספר איך ניתן לעשות את זה בלינוקס (אפשר בעוד צורות, ואני בטוח שחלקן יותר אלגנטיות).
כמעט כל תוכנית לא גרפית בלינוקס קוראת מהקלט הסטנדרטי וכותבת לפלט הסטנדרטי. בשימוש של פקודות שרשור (|) של המעטפת (bash במקרה שלי) ניתן לשרשר את הפלט של תוכנית אחת לקלט של תוכנית שניה.
אבל לפני שאנחנו רצים, צריך לגלות איך מוצאים אם קובץ מסויים לא מכיל מחרוזת.
הפקודה שקופצת לראש אוטומטית כשרוצים למצוא משהו היא grep. שמאפשרת מציאה של תבניות שבנויות כביטויים רגולריים, אבל grep לא בדיוק מתאימה כי היא עובדת ברמת שורה, ואנחנו רוצים לעבוד ברמת קובץ.
במילים אחרות, נוכל למצוא בעזרת grep שורות מסויימות שלא מכילות את המילה, אבל זה לא מה שאנחנו רוצים.
למרבה המזל, grep מחזירה ערך לbash, שניתן לפרש כאמת אם ורק אם הקובץ מכיל את המחרוזת.
אז כדי לבדוק אם קובץ מכיל מחרוזת נריץ את זה:
[code lang="bash"]
grep -q WORD file.txt || echo Not found
[/code]
הפרמטר -q נועד לבטל הדפסות של שורות שמתאימות למחרוזת (כי זה לא מה שאנחנו רוצים).
הפקודה || היא פקודה למעטפת שאומרת שאומרת "או".
זה אומר שהערך של הביטוי כולו הוא אמת אם לפחות אחד משני הביטויים משני צידי ה|| הם אמת. במקרה שgrep מוצא את המילה הוא יחזיר אמת, ולכן המעטפת לא תפעיל את הצד השני של ה|| כי היא כבר יודעת שהביטוי כולו הוא אמת. במקרה והקובץ לא מכיל את המחרוזת grep יחזיר שקר ואז המעטפת תריץ את הפקודה השניה שתדפיס לנו שלא מצאנו את הביטוי בקובץ.

עכשיו רק נשאר להריץ את הדבר הזה לכל קובץ, ולהדפיס עבור כל קובץ שלא מכיל את המחרוזת את השם שלו.
שוב, יש כמה דרכים, כולל שימוש בלולאה, אבל אני מעדיף את השימוש בxargs.
במצב הרגיל xargs מקבלת קלט ומפעילה פקודה כאשר הקלט הוא פרמטר של הפקודה. כאשר קוראים לxargs עם הפרמטר -i, הפקודה תקרא מספר פעמים, כמספר השורות בקלט, כאשר בכל פעם הסימן {} יוחלף בשורה הנוכחית.
לדוגמה, אם נפעיל את הפקודה ls -1, שמדפיסה כל קובץ בשורה נפרדת, על ספריה שמכילה קובץ a וקובץ b, ונשרשר לxargs -i נקבל:
[code lang="bash"]
$ ls -1 | xargs -i echo 123 {} 456
123 a 456
123 b 456
[/code]

xargs קוראת לecho פעם אחת עבור כל קובץ.
אם נרצה לעשות משהו טיפה יותר מחוכם, שכולל קריאות לכמה פקודות עבור כל קובץ, נוכל להפעיל bash עם פרמטר -c (פקודה), למשל:
[code lang="bash"]
$ ls -1 | xargs -i bash -c "echo content of {} is;cat {}"
content of a is
hello
content of b is
world
[/code]

אחרי כל זה אנחנו כבר יודעים מספיק בשביל להבין איך להשיג את המטרה, שהיא למצוא את כל הקבצים שלא מכילים משהו:
[code lang="bash"]
ls -1 | xargs -i bash -c "grep -q WORD {} || echo {}"
[/code]

Facebook Comments

12 תגובות בנושא “שרשור פעולות בלינוקס”

  1. תודה על ls -1 – לא הכרתי את האופציה, והייתי זקוק לה המון פעמים.

    כל דוגמאות הקוד שלך מאבדות את הכיווניות הנכונה ב-RSS, אגב.

  2. קודם כל, אני מאד נגד מצבים בהם גירסה מקומית צריכה להתעכב בגלל מחסור בתרגום; אין שום בעיה שהמתרגם יעבוד דרך ה־SVN, כאשר מגדירים לו מראש deadline לפני שיחרור גירסה.

    לכלים web־ים לצפיה בעדכוני svn אפשר להתחבר עם RSS; בצורה כזו המתרגמים יכולים לספק גירסה מעודכנת במרחק קצר מעדכון קובץ המקור, ואתה יכול לראות את העדכונים שלהם.

    ראיתי גם מקרים בהם צוות המתחזקים דוחפים את המחרוזות החדשות לסוף קובץ התרגום בתור הערות, ואז המתרגם צריך פשוט להוריד את הגירסה החדשה מהשרת ולהעלות את התיקונים שלו. אני חושב גם שכל הסקריפטים שאתה צריך בשביל להשוות תרגומים כבר קיימים; אני הייתי בודק קודם־כל ב־translate.sourceforge.net. אגב, יש להם גם מערכת וובית לניהול תרגומים, אם אתה מעוניין.

  3. עוד כמה אפשרויות

    for i in $(ls); do cat $i|tr -d \\n|grep /^.*WORD || echo "FOUND";done
    find . -exec gawk 'BEGIN{RS="WORD"} NR == 2{exit 1}' "{}" && echo $?

    אבל הכי פשוט:

    grep -L -e PATTERN *

    למה להסתבך?

  4. תומר, למה לקפוץ למסקנות?
    1. המתרגמים עובדים עם SVN.
    2. אני לא מעכב גרסאות בגלל תרגומים, מי שלא תרגם בזמן, נורא חבל. כשהוא יתרגם אני אעדכן את הגרסא. (אני לא מוציא גרסא חדשה בשביל תרגום).

    אלעזר,
    תודה, לא הכרתי את -L.
    בכל מקרה, אם לא נסתבך איך נכתוב פוסט על שרשור פקודות בלינוקס?

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

  6. תומר, המתרגמים משתמשים בpoedit שסורק את הקוד ונותן רשימה של דברים שצריכים תרגום (שינויים או תוספות).
    http://firestats.cc/wiki/TranslateFireStats
    http://firestats.cc/wiki/HowToMaintainTranslation

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

  7. ואיך שתי האפשרויות הקודמות? בזבזת לי שעות עבודה יקרות כשניסיתי לנפות אותן משגיאות עד שהגעתי לזה?

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

  9. לא הבנתי מה רע בתחביר של באש, יותר טוב תחביר נוסח ג'אווה של

    for (;i.hasNext();i = i.next() {
    system(i);
    }

סגור לתגובות.