הפילוסופיה של יוניקס (ושל לינוקס) היא שכל כלי יעשה פעולה אחת, ויעשה אותה כמו שצריך – והמשתמש יחבר את הכלים כדי להשיג את המטרה שלו.
הפילוסופיה הזו שונה מזו של מערכות הפעלה יותר "ידידותיות", שנותנות למשתמש כלים שעושים את מה שהמתכנתים חשבו שהמשתמש ירצה לעשות – לא פחות ולא יותר.
מצד אחד אותם משתמשים ישיגו את המטרה שלהם – אם המתכנתים חשבו שהיא מטרה ראויה – יותר בקלות, מצד שני, אם המתכנתים לא חשבו שהמטרה הזו מספיק חשובה או אם הם בכלל לא חשבו על הצורך המסויים המשתמש פשוט לא יוכל להשיג את המטרה בעזרת אותם כלים ידידותיים.
אז אחרי ההקדמה הזו, הנה דוגמא עם בעיה אמיתית:
ל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]