基於 systemd 的背景執行程式與備忘錄的方法
March 10, 2024•703 words
我目前主要是用 nushell 。nushell 沒有原生支援 backend process有點困擾。又我又很常在 nvim terminal 下執行指令,因此能夠在背景執行又不依賴現在 termianl很重要
過去我有用 pueue 做為背景執行程式,不過使用上沒有很順手(我不喜歡每次看東西都要先查 id)。於是考慮用 systemd-run 實做一個。目前感覺起來很可以。
systemd-run 執行程式時,需要指定 unit。除非程式正常結束,否則 unit 不能重複(當然這行為是根據你參數而定,目前我是這樣使用)
以下程式碼皆是 nushell 語法
def --wrapped "run" [ --doc="", ...command ] {
mut desc = ($command | str join ' ')
if ($doc != "") {
$desc = $doc
}
let _unit = (not-used-units|first)
systemd-run --user -u $_unit --service-type=oneshot -d --no-block --description $desc ...$command
$_unit
}
目前我的 unit 範圍是 "run", "run1", "run2", ..., "run9"。有太多 unit 可以使用就和用 pueue 沒兩樣了
當程式丟到 systemd 執行後,我會想要知道有哪些工作已經完成,有哪些正在執行,有哪些失敗。因為我很常在下指令,因此就做在 prompt
def bg-running [ ] {
running-units | each {|it| $"[($it.Id|str replace '.service' ''|str replace 'run' ''):(if ($it.ActiveState == "inactive") {"⏰"})(if ($it.ActiveState == "failed") {"❌"})($it.Description)]"} | str join ' '|str trim
}
這樣我就可以很方便的在 shell prompt 中看到我放到 systemd 中的程序狀態了
如果想要看程式執行結果,可以透過 journalctl 指令
def log [ $unit?:string@all-unit-name , --follow (-f)] {
let stdin = $in
mut extra = [ ]
mut _unit = $unit
if $follow {
$extra = ($extra | append ["-f"])
}
if ($_unit == null) {
$_unit = $stdin
$extra = ($extra | append ["-f"])
}
if ($env.IN_VIM? == "1") {
journalctl --user -u $_unit -e --no-hostname --no-pager ...$extra
} else {
journalctl --user -u $_unit -e --no-hostname ...$extra
}
}
有時候我們將程序放進背景後,想要直接看 log 輸出了(不然就和 pueue 差不多),就可以用 nushell 函數組合
run ps |log
有時候會忘記東西放到哪個 unit,所以就寫個指令印出所有 unit 的 log
def all-log [ -n:int=5 ] {
all-unit-name | par-each -t 4 {|it| {name: $it, log: (journalctl --user -u $it -n $n --no-hostname)} }|sort-by name
}
既然我們的 prompt 會顯示背景執行的程式,那麼如果我把要記錄的資訊放到 sleep infinity
裡面,是不是就可以當作備忘錄了。於是多了一個備忘錄功能(目前是將資訊記錄在 unit 的 description 欄位)
def note [ -t="infinity", --after (-a): string="", text ] {
let _unit = (not-used-units|first)
mut extra = []
if ($after != "") {
$extra = [--on-active $after]
}
systemd-run --user -u $_unit --service-type=oneshot -d --no-block --description $"📓($text)" -G ...$extra sleep $t
}
就這樣完成了一個背景執行程式與備忘錄了
完整程式碼如下
$env.max_jobs = 9
def --wrapped "run" [ --doc="", ...command ] {
mut desc = ($command | str join ' ')
if ($doc != "") {
$desc = $doc
}
let _unit = (not-used-units|first)
systemd-run --user -u $_unit --service-type=oneshot -d --no-block --description $desc ...$command
$_unit
}
def not-used-units [ ] {
let running_unit_names = (all-unit-info|filter {|it|
$it.ExecStart? != null
}|get Id|each {|it| $it|str replace '.service' ''})
all-unit-name |filter {|it| $it not-in $running_unit_names}
}
def note [ -t="infinity", --after (-a): string="", text ] {
let _unit = (not-used-units|first)
mut extra = []
if ($after != "") {
$extra = [--on-active $after]
}
systemd-run --user -u $_unit --service-type=oneshot -d --no-block --description $"📓($text)" -G ...$extra sleep $t
}
def log [ $unit?:string@all-unit-name , --follow (-f)] {
let stdin = $in
mut extra = [ ]
mut _unit = $unit
if $follow {
$extra = ($extra | append ["-f"])
}
if ($_unit == null) {
$_unit = $stdin
$extra = ($extra | append ["-f"])
}
if ($env.IN_VIM? == "1") {
journalctl --user -u $_unit -e --no-hostname --no-pager ...$extra
} else {
journalctl --user -u $_unit -e --no-hostname ...$extra
}
}
def all-log [ -n:int=5 ] {
all-unit-name | par-each -t 4 {|it| {name: $it, log: (journalctl --user -u $it -n $n --no-hostname)} }|sort-by name
}
def show [ unit:string@running-units-complete ] {
systemctl --user status $unit
}
def get-systemd-info [ unit: string ] {
systemctl --user show $unit|lines|each {|it| split row '=' -n 2|{ $in.0 : $in.1 }}|reduce {|a, b| $a | merge $b}
}
def stop [ ...units:string@running-units-complete ] {
$units | par-each -t 2 {|unit|
if (systemctl --user show $unit|find 'ActiveState=inactive'|is-not-empty) {
systemctl --user stop $"($unit|str replace '.service' '').timer"
} else if (systemctl --user show $unit|find 'ActiveState=failed'|is-not-empty) {
systemctl --user reset-failed $unit
} else {
systemctl --user stop $unit
}
}
null
}
def clean [ ] {
running-units | par-each {|it| stop $it.Id }
null
}
def all-unit-name [ ] {
["run"] | append (seq 1 $env.max_jobs|each {|it| $"run($it)"})
}
def all-unit-info [ ] {
all-unit-name | par-each -t 4 {|it| get-systemd-info $it}
}
def running-units [ ] {
all-unit-info|filter {|it|
$it.ExecStart? != null
} | sort-by Id
}
def running-units-complete [ ] {
running-units | each {|it| {value: $in.Id , description: $in.Description}}
}
def bg-running [ ] {
running-units | each {|it| $"[($it.Id|str replace '.service' ''|str replace 'run' ''):(if ($it.ActiveState == "inactive") {"⏰"})(if ($it.ActiveState == "failed") {"❌"})($it.Description)]"} | str join ' '|str trim
}