circular-dependencies : 循環参照

あるファイルを require したときにその結果が空のオブジェクトとして返される問題
module.exports first, lazy require, dependency injectionなどの解決方法がある

import sys
import re

MAPPING = {
    'core_read.cpp': 'core_io.cpp',
    'core_write.cpp': 'core_io.cpp',
}

# Directories with header-based modules, where the assumption that .cpp files
# define functions and variables declared in corresponding .h files is
# incorrect.
HEADER_MODULE_PATHS = [
    'interfaces/'
]

def module_name(path):
    if path in MAPPING:
        path = MAPPING[path]
    if any(path.startswith(dirpath) for dirpath in HEADER_MODULE_PATHS):
        return path
    if path.endswith(".h"):
        return path[:-2]
    if path.endswith(".c"):
        return path[:-2]
    if path.endswith(".cpp"):
        return path[:-4]
    return None

files = dict()
deps: dict[str, set[str]] = dict()

RE = re.compile("^#include <(.*)>")

# Iterate over files, and create list of modules
for arg in sys.argv[1:]:
    module = module_name(arg)
    if module is None:
        print("Ignoring file %s (does not constitute module)\n" % arg)
    else:
        files[arg] = module
        deps[module] = set()

# Iterate again, and build list of direct dependencies for each module
# TODO: implement support for multiple include directories
for arg in sorted(files.keys()):
    module = files[arg]
    with open(arg, 'r', encoding="utf8") as f:
        for line in f:
            match = RE.match(line)
            if match:
                include = match.group(1)
                included_module = module_name(include)
                if included_module is not None and included_module in deps and included_module != module:
                    deps[module].add(included_module)

# Loop to find the shortest (remaining) circular dependency
have_cycle: bool = False
while True:
    shortest_cycle = None
    for module in sorted(deps.keys()):
        # Build the transitive closure of dependencies of module
        closure: dict[str, list[str]] = dict()
        for dep in deps[module]:
            closure[dep] = []
        while True:
            old_size = len(closure)
            old_closure_keys = sorted(closure.keys())
            for src in old_closure_keys:
                for dep in deps[src]:
                    if dep not in closure:
                        closure[dep] = closure[src] + [src]
            if len(closure) == old_size:
                break
        # If module is in its own transitive closure, it's a circular dependency; check if it is the shortest
        if module in closure and (shortest_cycle is None or len(closure[module]) + 1 < len(shortest_cycle)):
            shortest_cycle = [module] + closure[module]
    if shortest_cycle is None:
        break
    # We have the shortest circular dependency; report it
    module = shortest_cycle[0]
    print("Circular dependency: %s" % (" -> ".join(shortest_cycle + [module])))
    # And then break the dependency to avoid repeating in other cycles
    deps[shortest_cycle[-1]] = deps[shortest_cycle[-1]] - set([module])
    have_cycle = True

sys.exit(1 if have_cycle else 0)

[shell] btc daemonが起動していなかったら起動する

VPSでbtc daemonを運用としていると、何故か勝手に落ちることがあるので、バッチでnodeが動いているか確認して、動いていなかったら起動するコマンドをシェルスクリプトで実行する。

ログファイルに書き込む際に、command=”bitcoin-core.cli –getinfo –testnet | sudo tee -a ${log_FILE}”などとやってしまうと、retval=$?の返り値が、teeの方で判定されてしまうので、判定した後に書き込むとしないと行けない。

#!/bin/sh

today=$(date "+%Y%m%d")
time=$(date "+%Y%m%d %H:%M:%S")
log_FILE="./log/log_${today}.log"

if [ ! -e $log_FILE ]; then
    sudo touch ${log_FILE}
fi

command="bitcoin-core.cli --getinfo --testnet"
eval $command

retval=$?
if [ $retval -eq 0 ]
then
    echo "btc daemon is running. " $time | sudo tee -a ${log_FILE}
else
    echo "btc daemon is stopped. " $time | sudo tee -a ${log_FILE}
    # command="bitcoin-core.daemon -testnet -prune=1000"
    command="echo try again!"
    eval $command
fi

あとはこのシェルをcronで設定すれば良いだけですね。

### cronの設定
とりあえずバッチの実行時間は2時にしました。

$ dnf search crontabs
Failed to set locale, defaulting to C.UTF-8
AlmaLinux 8 – BaseOS 4.8 MB/s | 6.3 MB 00:01
AlmaLinux 8 – AppStream 7.1 MB/s | 12 MB 00:01
AlmaLinux 8 – Extras 36 kB/s | 20 kB 00:00
Extra Packages for Enterprise Linux 8 – x86_64 8.9 MB/s | 16 MB 00:01
nginx stable repo 26 kB/s | 55 kB 00:02
nginx mainline repo 77 kB/s | 135 kB 00:01
======================== Name Exactly Matched: crontabs ========================
crontabs.noarch : Root crontab files used to schedule the execution of programs

$ crontab -e

0 2 * * * /home/alma/command/btc_daemon.sh

$ systemctl restart crond
$ systemctl enable crond
$ systemctl status crond
● crond.service – Command Scheduler
Loaded: loaded (/usr/lib/systemd/system/crond.service; enabled; vendor prese>
Active: active (running) since Sat 2024-02-24 21:38:44 JST; 43s ago
Main PID: 900566 (crond)
Tasks: 1 (limit: 5868)
Memory: 1.1M
CGroup: /system.slice/crond.service
└─900566 /usr/sbin/crond -n

あとは、明日ログを見て、実行されているようであれば、bitcoin-core.daemon のコメントアウトを外します。
※シェルを使うようになったのかと思うと感慨深い…

[shell] コマンドを実行する

シェルでのコマンドの実行はevalで実行する

command="echo hoge"
eval $command

$ ./test.sh
hoge

終了コードで処理を変える

command="echo hoge"
eval $command

retval=$?

if [ $retval -eq 0 ]
then
    echo "成功しました!"
else
    echo "失敗しました"
fi

これをechoではなく、bitcoin-core.cli –getinfoにします。

command="bitcoin-core.cli --getinfo"
eval $command

retval=$?

if [ $retval -eq 0 ]
then
    echo "成功しました!"
else
    echo "失敗しました"
fi

値が1の時に起動するように書けばよさそうですね。

fish shell入門

ubuntu22.04にインストールします。

$ sudo apt-add-repository ppa:fish-shell/release-3
$ sudo apt update
$ sudo apt install fish
$ fish –version
fish, version 3.7.0

~/.config/ に対象フォルダがないぞ…

[shell] awkを使いたい

hoge.txt

1 2 3
4 5 6
awk '{print $1}' hoge.txt

$ bash cli.bash
1
4

awk '{print $0}' hoge.txt

$ bash cli.bash
1 2 3
4 5 6

awk '$1 ~ /^[0-9]/ { print $1; }' hoge.txt

$ bash cli.bash
1
4

sub(前, 後、入力文字列)

awk '$1 ~ /^[0-9]/ {sub("1", "10", $0); print $1; }' hoge.txt

$ bash cli.bash
10
4

置換、正規表現で検出しているのがわかります。

helpopts=$($bitcoin_cli -help 2>&1 | awk '$1 ~ /^-/ { sub(/=.*/, "="); print $1 }' )
//
commands=$(_bitcoin_rpc help 2>/dev/null | awk '$1 ~ /^[a-z]/ { print $1; }')

なるほど、ユーザのCLIの処理を実装するのはシェルスクリプトで書いていくのね。

[shell] localで複数宣言

複数宣言する際に、配列も交えて宣言ができる

myfunc(){
    local next words=() prev
    next="taiwan"
    prev="korea"
    words=("a" "b" "c")
    echo $prev
    echo ${words[@]}
}
myfunc

$ bash cli.bash
korea
a b c

これで、以下のlocalの宣言の意味もわかりますね。

_bitcoin_cli() {
    local cur prev words=() cword
    local bitcoin_cli

    # save and use original argument to invoke bitcoin-cli for -help, help and RPC
    # as bitcoin-cli might not be in $PATH
    bitcoin_cli="$1"

    COMPREPLY=()
    // 省略
}
    if ((cword > 5)); then
        case ${words[cword-5]} in
            sendtoaddress)
                COMPREPLY=( $( compgen -W "true false" -- "$cur" ) )
                return 0
                ;;
        esac
    fi

この COMPREPLY=( $( compgen -W “true false” — “$cur” ) ) は何をやっているかというと、COMPREPLYの中にcompgen … のコマンドを入れていますね。

COMMAND=()
COMMAND=( $(date -d tomorrow) )

echo ${COMMAND[@]}

つまりbitcoin_cliで打った時のコマンド処理を書き換えています。

[shell] caseとesac

while :
do
    read key
    case "$key" in
        "a" ) echo "aが入力されました";;
        "b" ) echo "bが入力されました";;
        "c" ) echo "cが入力されました";;
        "q" ) echo "終了します"
            break;;
    esac
done

exit 0

$ bash cli.bash
a
aが入力されました
b
bが入力されました
q
終了します

num=(2 3 4 5 6 7 8 9)
odds=(1 3 5 7 9)
odd=()
for i in ${num}; do
    case $i in $odds)
        odd+=($i)
        ;;
    esac
done
echo ${odd[@]}

なんか違うな〜

[shell] 配列とforループ

()で定義して、配列名[@]で渡すと、一つづつ処理される

langs=("C++", "C", "java", "rust")
for lang in ${langs[@]}; do
    echo $lang
done

数字としてキャストするには$(())と二重で囲む必要がある

num=(1 2 3 4 5)
ans=()
for i in ${num[@]}; do
    ans+=$((i*i))
done
echo ${ans[@]}

$ bash cli.bash
1491625

[shell] 関数の書き方

#!/bin/bash
say_hello() {
    echo "Hello, world!"
}

say_hello

say_hello_people() {
    echo "Hello, $1 and $2!"
}

say_hello_people taro hanako

$ bash cli.bash
Hello, world!
Hello, taro and hanako!

_bitcoin_rpc() {
    # determine already specified args necessary for RPC
    local rpcargs=()
    for i in ${COMP_LINE}; do
        case "$i" in
            -conf=*|-datadir=*|-regtest|-rpc*|-testnet)
                rpcargs=( "${rpcargs[@]}" "$i" )
                ;;
        esac
    done
    $bitcoin_cli "${rpcargs[@]}" "$@"
}

関数名の先頭にアンダーバー(_bitcoin_rpc)となっているのは衝突を防ぐため
localはローカル変数

i=10
func() {
    echo $i
    local i=12
    echo $i
}
func
echo $i

[shell] テストコマンドを書く

test -e auto_update.sh; echo $?

FILE="auto_update.sh"
if test -n "$FILE"; then
    echo "File is exist"
fi

$ sh test.sh
0
File is exist

インストール関連はシェルで書いて、テストもシェルで書くのね。なるほど、勉強になりますね。