2024年5月29日 星期三

Linux 中仿 tree 輸出的 Shell 檔案

Linux 中仿 tree 輸出的 Shell 檔案

2024-05-29 一個比較完整的檔案寫完了放在gist上 Tree.sh (github.com)




某些環境基於某些原因實在是沒辦法,只能自己自幹一個這邊給一個 GPT 的作品,最基本基本的功能是有了


tree.sh

#!/bin/bash

# Function to print the folder name with color
function print_folder() {
    local folder_name=$1
    local COLOR_RESET="\033[0m"
    local COLOR_FOLDER="\033[1;34m" # Blue color for folders
    echo -e "${COLOR_FOLDER}${folder_name}/${COLOR_RESET}"
}

# Function to recursively print the directory tree structure
function print_tree() {
    local directory=$1
    local prefix=$2
    local files=("$directory"/*)
    local last_index=$((${#files[@]} - 1))

    # Check if the directory is empty
    if [ "$files" = "$directory/*" ]; then
        return
    fi

    for i in "${!files[@]}"; do
        local file="${files[$i]}"
        local basename=$(basename "$file")
        local new_prefix="$prefix"

        if [ $i -eq $last_index ]; then
            echo -n "${prefix}└── "
            new_prefix="$prefix    "
        else
            echo -n "${prefix}├── "
            new_prefix="$prefix│   "
        fi

        if [ -d "$file" ]; then
            print_folder "$basename"
            print_tree "$file" "$new_prefix"
        else
            echo "$basename"
        fi
    done
}

# Check if a directory is provided as an argument
if [ -z "$1" ]; then
    echo "Usage: $0 <directory>"
    exit 1
fi

# Remove trailing slash from the directory if it exists
directory=$(echo "$1" | sed 's:/*$::')

# Print the initial directory with color
print_folder "$(basename "$directory")"
print_tree "$directory" ""


使用的話跟 tree 差不多沒兩樣先給執行權限

chmod +x tree.sh

然後使用

./tree.sh testdir


輸出結果

testdir/
├── file1.txt
├── file2.txt
├── subdir1/
│   ├── file2.txt
│   ├── file3.txt
│   ├── subsubdir1/
│   │   └── file4.txt
│   └── subsubdir2/
│       └── file5.txt
└── subdir2/
    ├── file3.txt
    ├── file6.txt
    └── subsubdir1/
        └── file7.txt




精簡高壓版本

邏輯一樣的只是完全不管可讀性,能壓能壓版本

tree(){
    [ -z "$1" ] && { echo "Usage: $0 <directory>"; exit 1; }
    print_folder() { echo -e "\\033[1;34m${1}/\\033[0m"; }
    print_tree() { local dir=$1 prefix=$2; local files=("$dir"/*)
        print_tree_core() { [ $1 -ge ${#files[@]} ] && return
            local idx=$1; local file="${files[$idx]}"
            local base=$(basename "$file") new_prefix="$prefix"
            ([ $idx -eq $((${#files[@]} - 1)) ]) && {
                echo -n "${prefix}└── "; new_prefix="$prefix    "
            } || { echo -n "${prefix}├── "; new_prefix="$prefix│   "
            }; ([ -d "$file" ] && {
                print_folder "$base"; print_tree "$file" "$new_prefix"
            } || echo "$base"); print_tree_core $(($idx + 1))
        }; [ "$files" = "$dir/*" ] && return || print_tree_core 0
    }; dir=$(echo "$1" | sed 's:/*$::')
    print_folder "$(basename "$dir")"; print_tree "$dir" ""
}

貼上之後直接呼叫 tree 就可以用了 (要是原本就有會被覆蓋功能,被蓋了重啟終端就好)

tree testdir



增加顯示有多少個檔案

tree(){
    [ -z "$1" ] && { echo "Usage: $0 <directory>"; exit 1; }
    print_folder() { echo -e "\\033[1;34m${1}/\\033[0m"; }
    print_tree() { local dir=$1 prefix=$2; local files=("$dir"/*)
        print_tree_core() { [ $1 -ge ${#files[@]} ] && return
            local idx=$1; local file="${files[$idx]}"
            local base=$(basename "$file") new_prefix="$prefix"
            ([ $idx -eq $((${#files[@]} - 1)) ]) && {
                echo -n "${prefix}└── "; new_prefix="$prefix    "
            } || { echo -n "${prefix}├── "; new_prefix="$prefix│   "
            }; [ -d "$file" ] && { dir_cnt=$((dir_cnt + 1))
                print_folder "$base"; print_tree "$file" "$new_prefix"
            } || { echo "$base"; file_cnt=$((file_cnt + 1))
            }; print_tree_core $(($idx + 1))
        }; [ "$files" = "$dir/*" ] && return || print_tree_core 0
    }; dir=$(echo "$1" | sed 's:/*$::')
    print_folder "$(basename "$dir")"; dir_cnt=0; file_cnt=0
    print_tree "$dir"; echo -e "\n$dir_cnt directories, $file_cnt files"
}


增加顯示隱藏檔案

tree(){
    [ -z "$1" ] && { echo "Usage: $0 <directory>"; exit 1; }
    print_folder() { echo -e "\\033[1;34m${1}/\\033[0m"; }
    print_tree() { local dir=$1 prefix=$2; local files
        files=("${dir}"/.* "${dir}"/*); files=(${files[@]/*\*/})
        print_tree_core() { [ $1 -ge ${#files[@]} ] && return
            local idx=$1; local file="${files[$idx]}"
            local base=$(basename "$file") new_prefix="$prefix"
            ([ "$base" == "." ] || [ "$base" == ".." ]) && {
                print_tree_core $(($idx + 1)); return
            }; ([ $idx -eq $((${#files[@]} - 1)) ]) && {
                echo -n "${prefix}└── "; new_prefix="$prefix    "
            } || { echo -n "${prefix}├── "; new_prefix="$prefix│   "
            }; [ -d "$file" ] && { dir_cnt=$((dir_cnt + 1))
                print_folder "$base"; print_tree "$file" "$new_prefix"
            } || { echo "$base"; file_cnt=$((file_cnt + 1))
            }; print_tree_core $(($idx + 1))
        }; [ ${#files[@]} -eq 0 ] && return || print_tree_core 0
    }; dir=$(echo "$1" | sed 's:/*$::')
    print_folder "$(basename "$dir")"; dir_cnt=0; file_cnt=0
    print_tree "$dir"; echo -e "\n$dir_cnt directories, $file_cnt files"
}





魔法咒語

最後是魔法咒語,特地用函式包起來是要把路徑後置到後面,方便貼上直接些改
_(){ d=$(realpath "$1"); echo "$d"; find "$1" | sort | sed '1d;s|^'"$1"'| |;s/\/\([^/]*\)$/|-- \1/;s/\/[^/|]*/|   /g'; };_ .

為什麼要用醜醜的 |-- 而不是更漂亮的 └── 是因為,用 Ascii 以外的符號很容易就噴亂碼,自己根據情況斟酌使用吧




ASCII 顏色碼 綠色成功 與 紅色失敗

ASCII 顏色碼 綠色成功 Success 與 紅色失敗



偶爾用到每次都要查 gpt 查到煩了寫一份備用下來

# 綠色成功
echo -e "\033[42mSuccess\033[0m"
# 紅色失敗
echo -e "\e[41mFailure\e[0m"



WSL 安裝 ssh 並更改 port 設置自動啟動

WSL 安裝 ssh 並更改 port 設置自動啟動

預設狀態是不會自動啟動的而且 port 可能與主機端有衝突,視情況可以自行改成 2222 口這樣可以避開 Windows 本身如果也有 ssh 的話。



安裝並配置 OpenSSH

首先,確保已安裝 OpenSSH 伺服器:

sudo apt update
sudo apt install openssh-server


1. 設定 SSH 端口與密碼登入

編輯 SSH 設定檔:

sudo nano /etc/ssh/sshd_config

  1. 找到 Port 行,並修改為你所要的或保持預設就 22
  2. 找到 PasswordAuthentication  行,並修改為 yes

Port 22
PasswordAuthentication yes

儲存並退出 (Ctrl+S 然後 Ctrl+X)。

這裡預設值其實本來就是 22 ,只是為了示範如何修改而特地寫出來


2. 設定 SSH 伺服器開機自動啟動

設置 systemd 服務管理器:

sudo systemctl enable ssh

必須開著 WSL 視窗才會生效(這服務 systemctl 其實是由 wsl.conf 文件間接啟動的)


如果是舊版的 wsl 不支持 systemctl 請這裡編輯 /etc/wsl.conf 文件:

sudo nano /etc/wsl.conf

在文件末尾添加以下內容:

command="service ssh start"

保存文件(按 Ctrl + O),然後退出編輯器(按 Ctrl + X)。


4. 重啟 WSL

在 Windows 命令提示字元或 PowerShell 中執行:

wsl --shutdown

然後再次打開 WSL 後,SSH 伺服器應該會自動啟動並使用設定的端口。


在 WSL 中執行以下命令確認 SSH 伺服器是否正在運行:
sudo service ssh status



從主機端以 SSH 連接 WSL

打開主機端的 PowerShell 嘗試從主機端連接 WSL:
ssh -p 22 your_username@localhost

這裡 -p 的選項預設值就是 22,如果沒有改 port 可以省略不打





[番外篇] 在 WSL 中添加允許的公鑰

在 WSL 中創建 .ssh 目錄並設置適當的權限:

mkdir -p ~/.ssh
chmod 700 ~/.ssh

已經存在則無需建立


將你想要使用的公鑰添加到 authorized_keys 文件中:

nano ~/.ssh/authorized_keys

已經存在則無需建立


將公鑰貼上到此文件中,然後保存並退出。設置 authorized_keys 文件的權限:

chmod 600 ~/.ssh/authorized_keys



參考

2024年5月26日 星期日

Python 如何使用 boxsdk 獲取 Box Accese Token

Python 如何使用 boxsdk 獲取 Box Accese Token

繼上一篇
CHG: Python 如何使用 Json 通過 JWT 請求獲取 Box Accese Token (不使用boxsdk)

詳細介紹了如何手動請求,這裡介紹 boxsdk 的使用方法



安裝 boxsdk[jwt]

參考這裡官方安裝的教學
box/box-python-sdk: Box SDK for Python (github.com)

他有分兩個版本記得後面要括號 jwt 會多裝一些基本套件

pip install "boxsdk[jwt]"

單純的執行就可以安裝好了



方法1 直接直接導入 json 檔案

參考自官方的這篇文章
JWT with SDKs - Box Developer Documentation

以及這裡的教學
BOX API(JWT認証)をPythonで使ったときの備忘録 #Python - Qiita

from boxsdk import JWTAuth
from boxsdk import Client

# JSON 檔案的路徑
CONFIG_FILE = 'config.json'

# 準備使用 BOX API
auth = JWTAuth.from_settings_file(CONFIG_FILE)
client = Client(auth)

# 獲取並顯示用戶資訊(API 測試)
user = client.user().get()
print(f'用戶名稱: {user.name} (ID: {user.id}), 電子郵件: {user.login}')

如此就能輕鬆導入了



方法2 手動導入 JWT物件

這裡是手動從 json 中抽取出數據,並導入 JWT 物件的方法

import json
from boxsdk import Client, JWTAuth

# 加載官方 JSON 檔案
with open('config.json') as json_file:
    config = json.load(json_file)

# 創建 JWTAuth 認證對象
auth = JWTAuth(
    client_id=config['boxAppSettings']['clientID'],
    client_secret=config['boxAppSettings']['clientSecret'],
    enterprise_id=config['enterpriseID'],
    jwt_key_id=config['boxAppSettings']['appAuth']['publicKeyID'],
    rsa_private_key_data=config['boxAppSettings']['appAuth']['privateKey'].replace('\\n', '\n'),
    rsa_private_key_passphrase=config['boxAppSettings']['appAuth']['passphrase']
)

# 獲取訪問令牌
access_token = auth.authenticate_instance()
print(f"訪問令牌: {access_token}")

# 使用訪問令牌創建 Box 客戶端
client = Client(auth)

# 認證並獲取當前用戶資訊
user = client.user().get()
print(f'用戶名稱: {user.name} (ID: {user.id}), 電子郵件: {user.login}')


本質上跟 方法1 跑的是一樣的代碼只是拉出來手動做而已。


2024年5月25日 星期六

Python 如何使用 Json 通過 JWT 請求獲取 Box Accese Token (不使用boxsdk)

Python 如何使用 Json 通過 JWT 請求獲取 Box Accese Token (不使用boxsdk)

方法是參考自這篇的步驟說明

SDKを使用しないJWT - Box Developerドキュメント

以及這個的官方示例後修改而來
samples-docs-authenticate-with-jwt-api/sample.3.py


如果需要在 PwerShell 上的版本可以參考這個 PsJwt 專案中的範例
PsJwt/Example/Get-BoxToken at main · hunandy14/PsJwt (github.com)


下面讓我們開始正文吧如何一步一步的獲取到令牌



讀取 config.json 文件

這份文件可以通過這個教學一步步取得
SDKを使用しないJWT - Box Developerドキュメント

總之就是需要你在個人的 Box 帳號中註冊一個應用程式,並且在註冊後必須由經由該網域的Admin帳號同意權限請求才能使用。如果該帳號就是 Admin 則可以自己審核自己。

import json
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.serialization import load_pem_private_key

# 讀取 config.json 文件
with open('config.json') as json_file:
    config = json.load(json_file)

# 從配置中加載 PEM 私鑰
key = load_pem_private_key(
    data=config["boxAppSettings"]["appAuth"]["privateKey"].encode('utf8'),
    password=config["boxAppSettings"]["appAuth"]["passphrase"].encode('utf8'),
    backend=default_backend(),
)

這個很簡單就是把東西讀取出來而已,並把加密的私鑰先解密出來。



建立 JWT claims 聲明

建立 JWT claims 聲明

import secrets
import time

# 設定認證 URL
authentication_url = 'https://api.box.com/oauth2/token'

# 建立 JWT claims 用於認證
claims = {
    'iss': config['boxAppSettings']['clientID'],  # 客戶端 ID
    'sub': config['enterpriseID'],                # 企業 ID
    'box_sub_type': 'enterprise',                 # 認證類型
    'aud': authentication_url,                    # 受眾 URL
    'jti': secrets.token_hex(64),                 # JWT ID,用於唯一性
    'exp': round(time.time()) + 45                # 過期時間 (45 秒後)
}

這個聲明最終會變成發送給 Box 伺服器的資訊



建立 JWT assertions

有了 claims 之後,我們需要用這些聲明來生成一個 JWT token。這個 token 將會用來向 Box 的認證服務請求 Access Token。

import jwt

# 建立 JWT assertion
assertion = jwt.encode(
    claims, 
    key, 
    algorithm='RS512',
    headers={
        'kid': config['boxAppSettings']['appAuth']['publicKeyID']
    }
)

這段程式碼使用 pyjwt 庫來對 claims 進行簽名。key 是我們之前解密出的私鑰,而 algorithm 指定了我們使用 RS512 來進行簽名。

JWT token 驗證可以參考這個網站 JSON Web Tokens - jwt.io ,他可以動態驗證你的 JWT 是否正確,如果輸入私鑰也可以動態生成



發送請求以獲取 Access Token

我們現在需要將生成的 JWT token 發送到 Box 的認證伺服器,以換取一個 Access Token。

import requests

# 發送請求以獲取存取權杖
response = requests.post(
    authentication_url, {
        'grant_type': 'urn:ietf:params:oauth:grant-type:jwt-bearer',
        'assertion': assertion,
        'client_id': config['boxAppSettings']['clientID'],
        'client_secret': config['boxAppSettings']['clientSecret']
    }
)
# 解析回應並獲取存取權杖
access_token = response.json()['access_token']
print(f"AccessToken: {access_token}")

這裡使用 requests 庫發送 HTTP POST 請求到 Box 的認證伺服器。回應中包含了我們需要的 Access Token。

這個就是最終的成品了,可以拿來直接對 Box API 伺服器請求操作你的帳號了。



驗證 Access Token 

接下來,我們將使用獲取的 Access Token 來驗證身份,並且可以進一步操作 Box API。這邊就做一個簡單的測試查詢當前帳號是誰。

這部分就直接引用 boxsdk 測試比較快,目的只是測試獲取的令牌是否可用,也可以通過 response 的方式手動獲取請求。

from boxsdk import OAuth2, Client

# 使用 OAuth2 認證
auth = OAuth2(
    client_id=config['boxAppSettings']['clientID'],
    client_secret=config['boxAppSettings']['clientSecret'],
    access_token=access_token,
)

# 創建 Box 客戶端
client = Client(auth)
# 獲取用戶資訊
user = client.user().get()
print('Authenticated as:', user.name)

這段程式碼使用 boxsdk 庫來創建一個 Box 客戶端,並使用 Access Token 進行身份驗證。最後我們打印出已驗證用戶的名稱。



驗證 Access Token 2

這是直接透過 requests 跟 Box 伺服器交互的方法,會稍微麻煩一點,還得自己解構回傳的信息字串。

在這個方法中,我們不使用 boxsdk 庫,而是直接使用 requests 庫來發送 HTTP 請求,並手動處理回應。這樣可以更靈活地控制 API 請求和回應。

import requests

# 發送請求到 Box API 以獲取當前用戶資訊
response = requests.get(
    'https://api.box.com/2.0/users/me',
    headers={
        'Authorization': f'Bearer {access_token}',
    }
)

# 檢查請求是否成功
if response.status_code == 200:
    user_info = response.json()
    print('Authenticated as:', user_info['name'])
else:
    print('Failed to authenticate')
    print('Response:', response.text)

這段程式碼做了以下幾件事:

  1. 設置請求頭:使用獲取的 Access Token 設置請求頭中的 Authorization 字段。
  2. 發送 GET 請求:向 Box API 發送 GET 請求,以獲取當前用戶的資訊。請求的 URL 是 https://api.box.com/2.0/users/me
  3. 處理回應:檢查回應的狀態碼。如果狀態碼是 200,表示請求成功,然後解析回應的 JSON 數據並打印出用戶名稱。如果請求失敗,打印回應的錯誤信息。

通過這種方式,你可以手動處理 Box API 的所有回應,這在需要進行更複雜的錯誤處理或自定義 API 請求時特別有用。