מדריך מעשי ל - Tcl (חלק 1)

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

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

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

אחת מהשפות שבאופן עקבי מאלצת אותי להשתמש בדרכים לא קונבנציונליות היא Shell. קוד שתכתבו בלינוקס עם שימוש בכלים מ - util-linux, כמעט בוודאות יעשה לכם בעיות על BSD. הכלים שונים, האפשרויות אחרות ואף אחד היום לא כותב strict POSIX.

בגלל זה, כשאני צריך לכתוב קוד חוצה - Unix, אני מעדיף להשתמש ב - Tcl.

הקדמה

Tcl פותחה בסוף שנות ה - 80, והיא צברה פופולריות במהרה. התחביר שלה קל ופשוט להבנה, בלי כל מיני “שטויות” או תכנוני עיצוב “מתוחכמים” ששפות מודרניות מציגות. ניתן להרחיב אותה עם C בקלות. תנסו לממש את השפה בעצמכם, זה ילמד אותכם דבר או שניים בפיתוח שפת תכנות יעילה.

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

אחד מהיתרונות של Tcl, הוא שקל לממש את השפה, כיום המימוש הכי נפוץ (ומהיר) הוא 8.6, כל מימוש אחר של השפה כמו JimTcl יהיה יותר איטי. ולכן אני אשתמש ב - Tcl 8.6 שתוכלו להתקין עם מנהל החבילות של המערכת שלכם בקלות.

איך לקרוא את המדריך

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

קריאה מהנה!

בסיס

בואו נתחיל עם ה - Hello World! המסורתי, צרו קובץ בשם hello.tcl עם התוכן הבא:

puts "Hello, World!"

תריצו:

$ tclsh hello.tcl
Hello, World!

חשוב לזכור, כל דבר ב - Tcl הוא מחרוזת, לדוגמא:

set x pu
set y ts
$x$y "Hello, World!"

הפקודה set מגדירה משתנה, היא מקבלת שני ארגומנטים: שם המשתנה, והערך שלו. חשוב לזכור שבמידה והערך של המשתנה, מורכב מיותר ממילה אחת, חובה להקיף אותו במרכאות (“”) או ({}), על מנת שהוא ייחשב כארגומנט בודד (בדיוק כמו בשורת הפקודה):

puts Hello, World!   # Bad
puts "Hello, World!" # Good
puts {Hello, World}  # Good

הקפה במרכאות נקראת grouping.

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

puts -nonewline "Hello, World!"

משתנים

ב - Tcl ניתן להגדיר משתנה חדש עם הפקודה set:

set myvariable  "Hello, World!"

שם המשתנה יכול להכיל רווחים, כך תעשו זאת:

set {my variable} "Hello, World!"
puts ${my variable}

במידה ותרצו להגדיר משתנים ב - global scope, תוסיפו “::” לפני שם המשתנה:

# Bad
proc test {} {
    set xtest "Hello, World!"
}

test
puts $xtest # Error!

# Good
proc test {} {
    set ::xtest "Hello, World!"
}

test
puts $::xtest
-> Hello, World!

כאשר את מגדירים משתנים גלובלים, תמיד תגדירו אותם כמו בדוגמא האחרונה.

תגובות

כמו ב - Shell התגובות ב - Tcl מתחילות ב - #:

# This is a comment.
puts "Hello, World!"

במידה ותרצו להוסיף תגובה בסוף שורה, תעשו זאת כך:

puts "Hello, World!" ;# This is a comment.

פקודות

ניתן להריץ פקודות בכל שלב, בתוך ביטויים (בדיוק כמו ב - Shell) לדוגמא:

set name [puts John]
puts $name

set files [exec ls]
puts $files

בכדי להריץ פקודה בתוך ביטויי, הקיפו אותה ב - [ COMMAND ].

ביטויים ותנאים

הדוגמא הכי בסיסית:

if {1 == 1} {
    puts "yes"
} elseif {2 == 1} {
    puts "no"
} else {
    puts "no"
}

שימוש ב - switch:

puts -nonewline stderr "User: "
set user [gets stdin]

switch $user {
    "root" {
        puts "You can do anything."
    }
    "john" {
        puts "noop"
    }
    default {
        puts "I don't know."
    }
}

לולאות

לולאת foreach:

set mylst [glob /usr/include/*.h]

foreach item $mylst {
    puts $item
}

לולאת while:

set i 0

while {$i < 10} {
    puts $i
    set i [expr $i + 1]
}

לולאת for:

for {set x 0} {$x<10} {incr x} {
    puts "$x"
}

Procedure

על מנת להגדיר פקודה חדשה (Procedure) נשתמש בפקודה proc לדוגמא:

proc say_my_name name {
    puts "My name is: $name."
}

say_my_name John
-> My name is John

אפשר להשוות את הפקודה proc להגדרת פונקצייה חדשה בשפת תכנות סטנדרטית.

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

proc say_my_name {name age} {
    puts "Name: $name, Age: $age"
}

say_my_name "John X" 100
-> Name: John X, Age: 100

בברירת מחדל הערך שפקודה חדשה שנוצרה עם proc תחזיר, הוא הפלט של הפקודה האחרונה. לדוגמא:

proc hello {} {
    puts "Hello World"
}

set x [hello]
puts $x
-> Hello World

hello תחזיר Hello, World למרות שלא ציינו את זה עם return, ניתן להחזיר ערכים גם עם return כמובן:

proc hello {} {
    return "Hello World"
}

set x [hello]
puts $x
-> Hello World

הגדרת ערכי ברירת מחדל ל - Procedure:

proc default_name {{name John}, age} {
    puts "Name: $name, age: $age."
}

חשבון

בכדי לחשב מספרים נשתמש בפקודה expr לדוגמא:

set sum [expr 1 + 1 + 1 + 1 + [expr 13 * 10]]
puts $sum
-> 134

מחרוזות

חיבור מחרוזות עם הפקודה append:

set x "Hello, "
set y "World!"
set z [append x $y]
puts $z
-> Hello, World!

יותר קצר:

set x "Hello, "
set y "World!"
puts "$x$y"
-> Hello, World!

הפרדת מחרוזות על ידי התו \ :

set url https://bindh3x.io/file/file.txt
set file_name [lindex [split $url /] end]
puts $file_name

קבלת אורך מחרוזות:

set msg "Hello, World!"
puts [length $msg]

עבודה עם קבצים

קריאת 10 התווים הראשונים מקובץ /etc/passwd:

set fp [open /etc/passwd r]
puts [read $fp 10]
close $fp

קריאת קובץ שורה-שורה:

set fp [open /etc/passwd r]
set lines [split [read $fp] "\n"]

foreach line $lines {
    puts "LINE: $line"
}

close $fp

כתיבת המחרוזת - Hello levi לקובץ hello.txt:

set fp [open hello.txt w]
puts $fp "Hello, Levi"
close $fp

מערך (Array)

Tcl תומכת במערכים, ניתן להגדיר אותם בצורה הבאה:

array set myarray {hello world world hello}
puts $myarray(hello)
-> world

יותר קצר:

set myarray(hello)  world
puts $myarray(hello)

הוספת איבר למערך:

array set hello {new world}
puts $hello(new)
-> world

מחיקת איבר ממערך:

array set hello {hello world}
array unset hello hello

קבלת גודל מערך:

array set hello {hello world world hello}
puts [array size hello]
-> 2

הדפסת כל האברים במערך:

array set hello {hello world world hello}

foreach {key value} [array get hello] {
    puts $value
}

רשימה (List)

יצירת רשימה והדפסת האיבר הראשון:

set mylist  "hello world world hello"
puts [lindex 0]
-> hello

חיבור שני רשימות:

set list1 "10 9 8 7 6 5"
set list2 "1 2 3 4 5"
set clist [concat $list1 $list2]

הדפסת גודל הרשימה:

set mylst "hello world"
puts [llength $mylst]
-> 2

מילון (Dict)

יצירת מילון:

dict set colors red #F44336
dict set colors blue #1976D2
dict set colors green #7CB342

הדפסת כל הערכים במילון:

dict for {name color} $colors {
    puts "name: $name, color: $value"
}

הדפסת גודל המילון:

puts [dict size $colors]
-> 3

Packages

ב - Tcl ניתן לייבא חבילות בדיוק כמו import בשפת Python, הקוד שלפניכם ידפיס את כתובת ה - IP שלכם:

package require http

::http::config -useragent "curl/7.60.0"

set token [::http::geturl http://ipinfo.io/]
set data [::http::data $token]
regexp {\d+.\d+.\d+.\d+} $data ip

puts $ip

בדוגמא השתמשנו בחבילה http שמסופקת בברירת מחדל עם Tcl 8.6.

כרגע אני לא ארחיב על עבודה עם package ב - Tcl, מפני שזה הנושא של החלק הבא במדריך.

עבודה עם קבצי הגדרות

ב - Tcl יש פקודה בשם source שמאפשרת לעשות include לקובץ (בדיוק כמו ב - Shell). ניתן לנצל את האפשרות הזאת ליצירת קובץ הגדרות לסקריפט שלנו.

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

#!/usr/bin/env tclsh

source "$::env(HOME)/.config/clean.conf"

proc clean_log {level msg} {
    exec logger -p $level -t clean "$msg"
}

foreach f $::clean_files {
    set path $::clean_root/$f
    if {[file exists $path]} {
        clean_log info "Cleaning '$path'"
        file delete -force $path
    }
}

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

כך יראה קובץ ה - clean.conf שלנו:

# Root directory.
set ::clean_root "$::env(HOME)"

set ::clean_files {
    .config/dconf/
    .config/procps/
    .python_history
}

תריצו את הסקריפט:

chmod +x clean.tcl
./clean.tcl

ותבדקו ב - syslog, הסקריפט ידווח אם קובץ נמחק.

סיכום

זהו עד כאן החלק הראשון, בחלק השני נראה כיצד ניתן ליצור חבילות, ולעבוד עם הפקודה package.