跳至主要內容

建立 Ruby Gem

這篇備忘錄記錄了如何建立 Ruby Gem。

資訊

Ruby Gem 是一種打包和分發 Ruby 庫的標準方式。它包含了代碼、文檔、測試和元數據,使得 Ruby 程式碼的共享和重用變得非常容易。

1. 使用 Bundler 建立 Gem 骨架

Bundler 提供了一個方便的命令來生成一個新的 Gem 的基本結構。

bundle gem my_gem_name

這會創建一個名為 my_gem_name 的目錄,其中包含 Gem 的基本文件,例如:

  • my_gem_name.gemspec:Gem 的元數據文件。
  • lib/my_gem_name.rb:Gem 的主文件。
  • lib/my_gem_name/version.rb:版本文件。
  • Rakefile:Rake 任務,用於測試、發布等。
  • README.md:說明文檔。
  • LICENSE.txt:許可證文件。

2. 理解 Gem 結構

my_gem_name.gemspec

這是 Gem 的核心元數據文件。它包含了 Gem 的名稱、版本、描述、作者、依賴等信息。

# my_gem_name.gemspec
require_relative "lib/my_gem_name/version"

Gem::Specification.new do |spec|
spec.name = "my_gem_name"
spec.version = MyGemName::VERSION
spec.authors = ["Your Name"]
spec.email = ["[email protected]"]

spec.summary = "A short summary of MyGemName."
spec.description = "A longer description of MyGemName."
spec.homepage = "https://github.com/yourusername/my_gem_name" # 你的 GitHub 倉庫或其他主頁
spec.license = "MIT"
spec.required_ruby_version = Gem::Requirement.new(">= 2.3.0")

# 指定哪些文件應該包含在 Gem 中
spec.files = Dir.chdir(File.expand_path(__dir__)) do
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
end
spec.bindir = "exe"
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]

# 運行時依賴
# spec.add_dependency "rack", "~> 2.0"

# 開發時依賴 (測試、文檔生成等)
spec.add_development_dependency "bundler", "~> 2.0"
spec.add_development_dependency "rake", "~> 13.0"
spec.add_development_dependency "minitest", "~> 5.0"
end

lib/my_gem_name.rb

這是 Gem 的主入口文件,你可以在這裡定義你的模組和類。

# lib/my_gem_name.rb
require_relative "my_gem_name/version"

module MyGemName
class Error < StandardError; end
# 在這裡定義你的 Gem 邏輯
def self.greet(name)
"Hello, #{name} from MyGemName!"
end
end

lib/my_gem_name/version.rb

這個文件用於定義 Gem 的版本號。

# lib/my_gem_name/version.rb
module MyGemName
VERSION = "0.1.0"
end

3. 開發你的 Gem

lib/my_gem_name.rblib/my_gem_name/ 目錄下的其他文件中編寫你的 Ruby 程式碼。

範例:添加一個簡單的 Calculator 類。

# lib/my_gem_name/calculator.rb
module MyGemName
class Calculator
def add(a, b)
a + b
end

def subtract(a, b)
a - b
end
end
end

然後在 lib/my_gem_name.rb 中引入它:

# lib/my_gem_name.rb
require_relative "my_gem_name/version"
require_relative "my_gem_name/calculator" # 引入新的文件

module MyGemName
class Error < StandardError; end
def self.greet(name)
"Hello, #{name} from MyGemName!"
end
end

4. 測試你的 Gem

Bundler 生成的骨架通常會包含一個 test/spec/ 目錄。 你可以使用 MiniTest 或 RSpec 來編寫測試。

範例 (test/my_gem_name_test.rb):

require "minitest/autorun"
require "my_gem_name"

class MyGemNameTest < Minitest::Test
def test_that_it_has_a_version_number
refute_nil ::MyGemName::VERSION
end

def test_it_greets_correctly
assert_equal "Hello, World from MyGemName!", MyGemName.greet("World")
end

def test_calculator_add
calculator = MyGemName::Calculator.new
assert_equal 5, calculator.add(2, 3)
end
end

運行測試:

bundle exec rake test

5. 建立和安裝你的 Gem

建立 Gem

bundle exec rake build

這會在 pkg/ 目錄下生成一個 .gem 文件。

本地安裝 Gem

你可以將建立的 .gem 文件安裝到你的本地系統中,以便在其他項目中使用它:

gem install pkg/my_gem_name-0.1.0.gem

6. 發布你的 Gem (到 RubyGems.org)

  1. 創建 RubyGems.org 帳戶:如果還沒有,在 RubyGems.org 上註冊一個帳戶。
  2. 登錄
    gem push --help # 查看幫助
    你需要配置你的 API 密鑰。
  3. 推送到 RubyGems.org
    bundle exec rake release
    或者直接使用 gem push
    gem push pkg/my_gem_name-0.1.0.gem

確保你的 Gem 名稱在 RubyGems.org 上是唯一的。

總結

建立 Ruby Gem 是一個相對直接的過程,Bundler 提供了很好的起點。通過理解 gemspec 文件、組織你的程式碼、編寫測試以及正確發布,你可以輕鬆地與 Ruby 社區分享你的程式碼。

建立自製發行版

這篇備忘錄記錄了如何建立自製的 Linux 發行版。

資訊

建立一個自製的 Linux 發行版是一個複雜的過程,它涉及選擇核心組件、配置構建環境、編譯軟體包、打包系統以及創建安裝介質。這通常是為了特定的目的或學習目的而進行的。

核心組件

一個基本的 Linux 發行版通常包含以下核心組件:

  • Linux 核心 (Kernel):操作系統的核心。
  • Glibc (GNU C Library):C 語言標準庫。
  • Binutils (GNU Binary Utilities):編譯器工具鏈的一部分。
  • GCC (GNU Compiler Collection):編譯器。
  • Coreutils (GNU Core Utilities):基本命令行工具(ls, cp, mv 等)。
  • Bash (GNU Bash):Shell。
  • Filesystem Hierarchy Standard (FHS):文件系統佈局標準。
  • Init 系統:啟動和管理系統進程(例如 Systemd、SysVinit、OpenRC)。

建立步驟概覽

建立自製發行版通常遵循類似於 Linux From Scratch (LFS) 的過程。以下是一個高階的步驟概覽:

1. 準備構建環境 (Host System)

你需要一個現有的 Linux 系統作為主機來進行構建。這個主機系統將提供編譯工具和必要的庫。

2. 創建分區和掛載文件系統

為你的新發行版創建獨立的分區(例如 //bootswap)並將它們掛載到主機系統上的臨時目錄(例如 /mnt/lfs)。

3. 下載源碼

下載所有你需要編譯的軟體包的源碼,例如 Linux 核心、Glibc、Binutils、GCC 等。

4. 構建基本的工具鏈 (Cross-Compilation)

這是最關鍵也是最複雜的步驟之一。你需要構建一個獨立的、自給自足的工具鏈,它可以在新系統上編譯其他軟體。這通常涉及:

  • 構建 Binutils 的第一階段。
  • 構建 GCC 的第一階段(只包含 C)。
  • 構建 Glibc 的第一階段。
  • 重新構建 Binutils 和 GCC,使其鏈接到新的 Glibc。

5. 構建臨時系統

使用新構建的工具鏈,編譯和安裝一系列基本的實用程式,例如:

  • Zlib
  • File
  • Readline
  • Bash
  • Coreutils
  • Sed
  • Tar
  • Xz
  • Grep
  • ...等

這一步的目的是創建一個最小但功能齊全的 Linux 環境,以便在其中繼續構建。

6. 進入新系統 (Chroot)

使用 chroot 命令將根文件系統切換到你正在構建的新系統的目錄。從現在開始,所有命令都將在新系統的環境中執行。

chroot /mnt/lfs /usr/bin/env -i
HOME=/root TERM="$TERM"
PS1='(lfs chroot) \u:\w\$ '
PATH=/usr/bin:/usr/sbin
/bin/bash --login

7. 構建其餘的系統軟體包

在新系統中,編譯和安裝其餘的關鍵軟體包,包括:

  • 完整的 Glibc
  • 完整的 GCC
  • Linux 核心標頭
  • M4
  • Ncurses
  • Procps
  • Util-linux
  • E2fsprogs (文件系統工具)
  • GRUB (引導加載器)
  • ...等

8. 配置系統

配置網絡、用戶、密碼、時區、語言環境等。

9. 創建啟動腳本和配置文件

  • /etc/fstab:文件系統掛載點。
  • /etc/passwd, /etc/group:用戶和組。
  • /etc/network/interfacessystemd-networkd 配置。
  • Init 系統的配置。

10. 安裝核心

編譯和安裝最終的 Linux 核心,並創建 initramfs

11. 安裝引導加載器

配置和安裝 GRUB,使其能夠引導你的新發行版。

12. 退出 Chroot 並清理

chroot 環境中退出,卸載文件系統,並清理臨時文件。

13. 重啟並測試

從新建立的發行版啟動系統。

進階主題

  • 套件管理:考慮使用或開發一個簡單的套件管理器。
  • 桌面環境:安裝 Xorg、桌面環境(如 GNOME、KDE、XFCE)和相關應用程式。
  • Live CD/USB:創建一個可啟動的 Live 系統。

總結

建立一個自製的 Linux 發行版是一項艱巨但非常有益的學習經歷,它能讓你深入理解 Linux 系統的底層運作方式。遵循 LFS 的方法是一個很好的起點,但也可以根據你的具體需求進行客製化和簡化。

安裝 Firefox 建置版本

Ubuntu 22.04 似乎預設安裝了 snap 版本的 Firefox,在某些環境下無法啟動,因此記錄如何安裝預先建置的 Firefox。

移除 apt / snap 版本的 Firefox

sudo apt purge firefox
sudo snap remove firefox

安裝 Firefox 建置版本

# 下載
wget "https://download.mozilla.org/?product=firefox-latest-ssl&os=linux64&lang=ja" --trust-server-names

# 解壓縮
tar xvf firefox-*.tar.bz2

# 安裝
sudo cp -r firefox /usr/lib

# 建立執行檔的符號連結
sudo ln -s /usr/lib/firefox/firefox /usr/bin/firefox

# 下載並放置桌面捷徑檔案
sudo mkdir -p /usr/share/applications
sudo wget https://bit.ly/3Mwigwx -O /usr/share/applications/firefox.desktop

Ubuntu 上的 Dock

這篇備忘錄記錄了 Ubuntu 上的 Dock。

資訊

Ubuntu 的 Dock(也稱為啟動器或側邊欄)是 GNOME 桌面環境中的一個組件,它通常位於屏幕的左側,用於快速啟動應用程式和管理當前正在運行的窗口。

1. Dock 的基本操作

  • 啟動應用程式:點擊 Dock 上的應用程式圖標即可啟動。
  • 切換窗口:如果應用程式有多個窗口,點擊圖標會顯示所有窗口的縮略圖,你可以選擇要切換的窗口。
  • 添加/移除應用程式
    • 添加到 Dock:右鍵點擊 Dock 上正在運行的應用程式圖標,然後選擇「加入最愛」。
    • 從 Dock 移除:右鍵點擊 Dock 上的應用程式圖標,然後選擇「從最愛移除」。
  • 拖放:你可以將應用程式圖標拖放到 Dock 上進行重新排序。

2. 配置 Dock

Ubuntu 提供了多種方式來配置 Dock 的行為和外觀。

A. 使用「設定」應用程式

這是最簡單直觀的方法。

  1. 打開 設定
  2. 導航到 外觀 (或在舊版本中是 Dock)。
  3. 你可以配置以下選項:
    • Dock 位置:將 Dock 放置在屏幕的左側、底部或右側。
    • Dock 大小:調整 Dock 圖標的大小。
    • 自動隱藏 Dock:當窗口最大化或接近 Dock 時,自動隱藏 Dock。
    • 顯示個人主目錄、磁碟機等:選擇是否在 Dock 上顯示可移動媒體和網絡卷。
    • 顯示應用程式菜單:在 Dock 上顯示應用程式菜單按鈕。

B. 使用 GNOME Tweaks 工具 (推薦用於更多自定義)

GNOME Tweaks(以前稱為 GNOME Tweak Tool)提供了更多進階的選項來自定義 GNOME 桌面環境,包括 Dock。

  1. 安裝 GNOME Tweaks
    sudo apt install gnome-tweaks
  2. 啟動 GNOME Tweaks: 在應用程式菜單中搜索「Tweaks」或在終端中運行 gnome-tweaks
  3. 配置 Dock: 導航到「Extensions」(擴展),找到「Dash to Dock」擴展(如果已安裝,Ubuntu 默認的 Dock 實際上是 Dash to Dock 的一個變體)。 在這裡,你可以找到更多控制 Dock 行為的選項,例如:
    • 智能隱藏:更精細的自動隱藏控制。
    • 應用程式圖標行為:點擊應用程式圖標時的行為。
    • 自定義 Dock 主題:更改 Dock 的視覺樣式。

C. 使用命令行 (gsettings)

對於更精確的控制或腳本化配置,你可以使用 gsettings 命令。

範例:

  • 將 Dock 放置在底部:
    gsettings set org.gnome.shell.extensions.dash-to-dock dock-position 'BOTTOM'
  • 設置 Dock 圖標大小:
    gsettings set org.gnome.shell.extensions.dash-to-dock dash-max-icon-size 32
  • 啟用自動隱藏:
    gsettings set org.gnome.shell.extensions.dash-to-dock autohide true

要查看所有可用的 Dash to Dock 設置:

gsettings list-keys org.gnome.shell.extensions.dash-to-dock

總結

Ubuntu 的 Dock 是其桌面體驗不可或缺的一部分,它提供了方便的應用程式啟動和窗口管理功能。通過內置的「設定」應用程式、GNOME Tweaks 工具或命令行 gsettings,你可以根據自己的喜好靈活地自定義 Dock 的外觀和行為。

OpenLDAP 中 slappasswd 產生的 SSHA 是什麼?

slappasswd 指令是什麼?

slappasswd 指令是用來為 OpenLDAP 產生密碼的工具,預設使用 SSHA 對密碼進行雜湊處理。

認證機制

在 SSHA 中,產生的雜湊值最後 4 個位元組為鹽值(salt)。認證時,系統會將輸入的密碼與儲存的鹽值組合後產生雜湊,並比對是否與儲存的雜湊相符。

以下程式在輸入正確密碼(例如 admin)時,會產生與原始雜湊相同的結果。

require 'base64'
require 'digest'

pass = 'admin'
ssha = '{SSHA}23AUBfRZytVFNpe7onuFhyCSJOHRzCWh'
ssha =~ /{.+}(.+)/
salt256s = Base64.decode64(Regexp.last_match(1)).unpack('C*'[-4..-1])

salt = salt256s.pack('C*')
b_ssha = Digest::SHA1.digest(pass + salt)
Base64.strict_encode64(
(b_ssha.unpack('C*') + salt256s).pack('C*')
)

[EOL]

部落格環境遷移

我們已將部落格環境從 Jekyll 遷移至 Docusaurus。 我們計劃將 Jekyll 中點閱率較高的文章一併移轉過來。

在 Python 偵測極值

摘要

偵測訊號的極值。

訊號的產生

舉例產生一個偽造的訊號。

  • 3 [Hz] 的訊號 + 0.01 [Hz] 的訊號 + 雜訊
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
from scipy import signal

t = np.arange(30 * 100) / 100
x = np.sin(2 * np.pi * t / 3) + np.random.randn(len(t)) * 0.2 + np.sin(2 * np.pi * t / 100)
df = pd.DataFrame({'Data': x}, index=pd.to_datetime(t, unit='s').time)

fig, ax = plt.subplots()
df.plot(ax=ax, xlim=['00:00:00', '00:00:10'])
plt.show()

tmp

檢查訊號的特性

def acorr(df: pd.DataFrame, ra: int = 3, fs=100):
x = np.correlate(df.Data.values, df.Data.values, mode='full')
t = (np.arange(len(x)) - len(x) / 2 + 0.5) / fs
x /= x.max()
fig, ax = plt.subplots()
ax.set_xlim(-ra, ra)
ax.set_ylim(0, 1)
ax.plot(t, x)
ax.set_title('Autocorrelation')
ax.grid(axis='x')
ax.set_xticks(np.linspace(-ra, ra, 2 * ra + 1))
plt.show()

acorr(df)

可以看出主要的訊號是 1/3 [Hz]。因此,對 1/3 [Hz] 施加低通濾波器。

tmp

套用低通濾波器並偵測極值

套用低通濾波器以便偵測極值。

如果在 1/3 [Hz] 施加低通濾波器仍無法正確偵測,請逐步降低頻率。

tmp

fs = 10
lpf = 0.1
b, a = signal.butter(5, lpf / fs * 2, 'low')
df['Filter'] = signal.filtfilt(b, a, df.Data.values)
max_idx = signal.argrelmax(df['Filter'].values)
min_idx = signal.argrelmin(df['Filter'].values)

df['max_index'] = False
df.iloc[max_idx[0], 2] = True
df['min_index'] = False
df.iloc[min_idx[0], 3] = True

fig, ax = plt.subplots()
df.plot(ax=ax)
ax.set_xlim(['00:00:00', '00:00:30'])
ax.scatter(
df.loc[df['max_index'], ['Filter']].index,
df.loc[df['max_index'], ['Filter']],
color='tab:orange',
zorder=3)
ax.scatter(
df.loc[df['min_index'], ['Filter']].index,
df.loc[df['min_index'], ['Filter']],
color='tab:green',
zorder=3)
plt.show()

C 語言的二維陣列處理

用結構體將行數、列數與儲存數值的記憶體整合在一起,會更方便操作。

#include <stdio.h>
#include <stdlib.h>

typedef struct {
float *data;
int col_size;
int row_size;
} Mat;

void MatInit(Mat *mat, int row_size, int col_size) {
mat->row_size = row_size;
mat->col_size = col_size;
mat->data = (float *)calloc(row_size * col_size, sizeof(float));
}

float *MatAt(Mat *mat, int i, int j) {
return mat->data + i * mat->col_size + j;
}

void MatFree(Mat *mat) { free(mat->data); }

int main(void) {
Mat mat;
MatInit(&mat, 30, 5); // 初始化 30 行 5 列的矩陣
*MatAt(&mat, 0, 0) = 50; // 將第 0 行第 0 列設為 50
printf("%f\n", *MatAt(&mat, 0, 0)); // 顯示第 0 行第 0 列的數值

MatFree(&mat);

return 0;
}

從安裝腳本中得到錯誤的結果

這篇備忘錄記錄了當我從安裝腳本中得到錯誤結果時的解決方法。


問題描述

有時,當我運行某些安裝腳本(例如來自 GitHub 的自動安裝腳本或官方提供的 install.sh)時,會遇到以下錯誤信息:

Got bad result from install script

這個錯誤通常表示腳本執行不成功,或者沒有返回預期的成功代碼。這可能是由於多種原因造成的,例如:

  • 網絡問題:下載資源失敗。
  • 權限問題:腳本沒有足夠的權限執行某些操作。
  • 依賴缺失:系統中缺少腳本所需的工具或庫。
  • 環境不兼容:腳本是為不同環境設計的,或者當前環境變量不正確。
  • 腳本本身的問題:腳本有 bug。

解決方案

1. 檢查網絡連接

確保你的設備連接到互聯網,並且沒有任何防火牆或代理設置阻止腳本訪問外部資源。

ping google.com

2. 檢查權限

確保你以正確的權限運行腳本。對於需要系統級更改的腳本,通常需要使用 sudo

sudo bash install.sh

3. 安裝缺失的依賴

仔細閱讀腳本的輸出,查找是否有關於缺少命令或庫的錯誤信息。通常,腳本作者會在 README 中列出先決條件。

常見的依賴包管理器命令:

  • Ubuntu/Debian:sudo apt install <package_name>
  • CentOS/Fedora:sudo yum install <package_name>sudo dnf install <package_name>
  • macOS (Homebrew):brew install <package_name>

4. 檢查環境變量

某些腳本依賴特定的環境變量。確保你的 PATH 設置正確,並且所有必要的環境變量都已導出。

你可以使用 env 命令查看當前的環境變量。

5. 逐行執行腳本 (調試)

如果上述方法都無效,你可以嘗試逐行執行腳本,以找出問題的確切位置。

  1. 打開腳本
    vim install.sh
  2. 在腳本開頭添加 set -x: 這會讓 Shell 打印出每個執行的命令,幫助你追蹤問題。
    #!/bin/bash
    set -x
    # ... 腳本其餘內容
  3. 運行腳本:觀察輸出,當腳本失敗時,最後一個打印的命令通常是導致問題的原因。

6. 查閱腳本的 GitHub 頁面或文檔

如果腳本來自開源項目,請訪問其 GitHub 儲存庫。查看 Issues 頁面,看看是否有其他人遇到過相同的問題,或者在 Wiki/文檔中查找是否有特定的故障排除指南。

7. 嘗試不同的安裝方式

如果腳本提供的安裝方式失敗,看看項目是否提供了替代的安裝方式,例如:

  • 手動編譯
  • Docker 容器
  • 包管理器安裝(如 apt, yum, brew, npm, pip 等)

總結

遇到 "Got bad result from install script" 錯誤時,不要慌張。系統地檢查網絡、權限、依賴和環境變量,並利用調試工具,通常可以找到問題的根源並解決它。

NMF (HALS) 的實作

import numpy as np

X = np.array([[1, 1], [2, 1], [3, 1.2], [4, 1], [5, 0.8], [6, 1]])

n_components, n_samples, n_features, = (2,) + X.shape
W = np.random.uniform(size = (n_samples, n_components))
H = np.random.uniform(size = (n_components, n_features))

eps = 1e-4

# NMF
for i in range(100):
# update B
A = X.T.dot(W)
B = W.T.dot(W)
for j in range(n_components):
tmp = H[j, :] + A[:, j] - H.T.dot(B[:, j])
H[j, :] = np.maximum(tmp, eps)

# update A
C = X.dot(H.T)
D = H.dot(H.T)
for j in range(n_components):
tmp = W[:, j] * D[j, j] + C[:, j] - W.dot(D[:, j])
W[:, j] = np.maximum(tmp, eps)
norm = np.linalg.norm(W[:, j])
if norm > 0:
W[:, j] /= norm

print(W)
print(H)
print(W.dot(H))