#!/bin/sh

# ==============================================================================
# NOR Flash Lifetime Stress Test for Embedded Linux (BusyBox)
#
# Version: 1.2
#
# Description:
# Performs a continuous write/read/verify cycle on a NOR flash MTD partition.
# Creates a status file in /tmp for monitoring during long-running tests.
#
# WARNING: THIS SCRIPT IS DESTRUCTIVE. IT WILL ERASE ALL DATA ON THE
#          SPECIFIED FLASH DEVICE. USE WITH EXTREME CAUTION.
#
# How to monitor:
# In another shell, run: cat /tmp/nor_stress_test_status.log
# ==============================================================================

# --- CONFIGURATION ---
FLASH_DEVICE="/dev/mtdblock5"
TEST_FILE_SIZE_KB=64
TEMP_DIR="/tmp"
# --- END OF CONFIGURATION ---


# --- SCRIPT LOGIC ---
SOURCE_FILE="${TEMP_DIR}/stress_source.dat"
READBACK_FILE="${TEMP_DIR}/stress_readback.dat"
SOURCE_MD5_FILE="${TEMP_DIR}/source.md5"
READBACK_MD5_FILE="${TEMP_DIR}/readback.md5"
STATUS_FILE="${TEMP_DIR}/nor_stress_test_status.log"

START_TIME=$(date)
passed_cycles=0
cycle_count=1

# Function to write the current status to a log file
update_status() {
  current_status_msg="$1"
  # Overwrite the status file with the latest info
  {
    printf "--- NOR Flash Stress Test Status ---\n"
    printf "Test Start Time:     %s\n" "${START_TIME}"
    printf "Last Update:         %s\n" "$(date)"
    printf "Total Cycles Passed: %d\n" "${passed_cycles}"
    printf "Current Cycle:       %d\n" "${cycle_count}"
    printf "Current Status:      %s\n" "${current_status_msg}"
  } > "${STATUS_FILE}"
}

# Function to clean up temporary files on exit
cleanup() {
  printf "\nCleaning up temporary files...\n"
  rm -f "${SOURCE_FILE}" "${READBACK_FILE}" "${SOURCE_MD5_FILE}" "${READBACK_MD5_FILE}"
  printf "Cleanup complete. Exiting.\n"
}

trap cleanup INT TERM EXIT

# --- Initial Sanity Checks ---
printf "============================================================\n"
printf "       NOR Flash Stress Test Script (v1.2)\n"
printf "============================================================\n"
printf "Target Device:     %s\n" "${FLASH_DEVICE}"
printf "Test Data Size:    %d KB\n" "${TEST_FILE_SIZE_KB}"
printf "Status Log File:   %s\n" "${STATUS_FILE}"
printf "------------------------------------------------------------\n"
printf "!!! WARNING: THIS WILL DESTROY ALL DATA ON %s !!!\n" "${FLASH_DEVICE}"
printf "------------------------------------------------------------\n"
printf "Type 'YES' to continue: "
read -r confirmation
if [ "${confirmation}" != "YES" ]; then
  printf "Confirmation not received. Aborting.\n"
  exit 1
fi

if [ ! -b "${FLASH_DEVICE}" ]; then
  printf "Warning: Device %s not found or is not a character device.\n" "${FLASH_DEVICE}"
fi

printf "Starting stress test. Press Ctrl+C to stop.\n"
printf "You can monitor progress by running 'cat %s'\n" "${STATUS_FILE}"

# --- Main Test Loop ---
while true; do
  printf "\n----- Cycle %d -----\n" "${cycle_count}"

  # 1. Create source file
  update_status "Creating random source file"
  printf "[1/5] Creating %d KB random source file...\n" "${TEST_FILE_SIZE_KB}"
  dd if=/dev/urandom of="${SOURCE_FILE}" bs=1k count="${TEST_FILE_SIZE_KB}" 2>/dev/null
  if [ $? -ne 0 ]; then
    update_status "ERROR: Failed to create source file. Test stopped."
    printf "Error: Failed to create source file. Aborting.\n"
    exit 1
  fi

  # 2. Calculate source MD5
  update_status "Calculating source MD5 hash"
  printf "[2/5] Calculating source file MD5 hash...\n"
  md5sum "${SOURCE_FILE}" > "${SOURCE_MD5_FILE}"
  read -r source_md5 _ < "${SOURCE_MD5_FILE}"
  printf "      Source MD5: %s\n" "${source_md5}"

  # 3. Write to flash
  update_status "Writing data to ${FLASH_DEVICE}"
  printf "[3/5] Writing data to %s...\n" "${FLASH_DEVICE}"
  dd if="${SOURCE_FILE}" of="${FLASH_DEVICE}" bs=1k 2>/dev/null
  if [ $? -ne 0 ]; then
    update_status "ERROR: Failed to write to flash. Test stopped."
    printf "Error: Failed to write to %s. Aborting.\n" "${FLASH_DEVICE}"
    exit 1
  fi

  # 4. Read back from flash
  update_status "Reading data back from ${FLASH_DEVICE}"
  printf "[4/5] Reading data back from %s...\n" "${FLASH_DEVICE}"
  dd if="${FLASH_DEVICE}" of="${READBACK_FILE}" bs=1k count="${TEST_FILE_SIZE_KB}" 2>/dev/null
  if [ $? -ne 0 ]; then
    update_status "ERROR: Failed to read from flash. Test stopped."
    printf "Error: Failed to read from %s. Aborting.\n" "${FLASH_DEVICE}"
    exit 1
  fi

  # 5. Verify data
  update_status "Verifying data integrity"
  printf "[5/5] Verifying data integrity...\n"
  md5sum "${READBACK_FILE}" > "${READBACK_MD5_FILE}"
  read -r readback_md5 _ < "${READBACK_MD5_FILE}"
  printf "      Readback MD5: %s\n" "${readback_md5}"

  if [ "${source_md5}" = "${readback_md5}" ]; then
    passed_cycles=$((passed_cycles + 1))
    printf "      SUCCESS: Hashes match. Cycle %d passed.\n" "${cycle_count}"
    update_status "Cycle passed. Waiting for next cycle."
  else
    failure_msg="FAILURE: HASH MISMATCH on cycle ${cycle_count}"
    update_status "${failure_msg}"
    printf "      !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"
    printf "      !!! %s !!!\n" "${failure_msg}"
    printf "      !!! Source MD5:   %s\n" "${source_md5}"
    printf "      !!! Readback MD5: %s\n" "${readback_md5}"
    printf "      !!! Flash may be failing. Aborting test. !!!\n"
    printf "      !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"
    mv "${SOURCE_FILE}" "${TEMP_DIR}/FAILED_source_cycle_${cycle_count}.dat"
    mv "${READBACK_FILE}" "${TEMP_DIR}/FAILED_readback_cycle_${cycle_count}.dat"
    exit 1
  fi

  cycle_count=$((cycle_count + 1))
done

实现指定目录下保留最新N个文件的Shell脚本:

#!/bin/bash

# 参数检查
if [ $# -ne 2 ]; then
    echo "Usage: $0 <directory> <number_of_files_to_keep>"
    exit 1
fi

directory="$1"
n="$2"

# 验证目录是否存在
if [ ! -d "$directory" ]; then
    echo "Error: Directory '$directory' does not exist."
    exit 1
fi

# 验证保留数量是否为合法数字
if ! [[ "$n" =~ ^[0-9]+$ ]]; then
    echo "Error: Invalid number format. Please enter a positive integer."
    exit 1
fi

# 安全删除旧文件的命令链
find "$directory" -maxdepth 1 -type f -printf "%T@ %p\0" | \
  sort -z -k1nr | \
  cut -z -d' ' -f2- | \
  tail -z -n +$(($n + 1)) | \
  xargs -0 -r rm -f -- 

echo "Success: Kept latest $n files in '$directory'."

使用示例:

# 保留/var/log目录下最新的5个文件
sudo ./keep_latest.sh /var/log 5

该脚本实现批量删除指定日期内久未删除的分支:

快捷使用

./git-cleanup.sh -d 90(天数)

自定义保护分支列表

  1. 创建保护分支列表文件:

    echo -e "production\nlegacy-system" > ~/.git_cleanup_protected
  2. 指定自定义配置文件:

    ./git-cleanup.sh -c ./project_protected_branches.cfg -d 60
  3. 配置文件格式说明:

    • 每行一个分支名称
    • 支持行末注释(以 # 开头的内容)
    • 自动忽略空行和空格
  4. 查看当前保护分支列表

    # 使用示例
    $ ./git-cleanup.sh -l
    当前受保护分支列表:
    ----------------------------------------
    分支名称                 来源
    ----------------------------------------
    production              [配置文件] /home/user/.git_cleanup_protected
    main                    [系统默认]
    master                  [系统默认]
    develop                 [系统默认]
    ----------------------------------------
    (配置文件路径: /home/user/.git_cleanup_protected)

脚本内容

git-cleanup.sh:

#!/bin/bash

# 默认配置
DEFAULT_PROTECTED=("master" "main" "develop")  # 默认受保护分支
CONFIG_FILE="${HOME}/.git_cleanup_protected"   # 默认配置文件路径
remote="origin"                                # 远程仓库名称
delete_local=true                              # 是否删除本地分支
days_threshold=30                              # 默认过期天数阈值

# 加载配置文件
declare -a protected_branches=()
load_protected_branches() {
    # 清空数组重新加载
    protected_branches=()

    # 读取配置文件
    if [ -f "$CONFIG_FILE" ]; then
        while IFS= read -r line; do
            line=${line%%#*}      # 去除行末注释
            line=${line//$'\r'/}  # 处理Windows换行符
            line=${line// /}      # 去除空格

            [[ -n "$line" ]] && protected_branches+=("$line")
        done < "$CONFIG_FILE"
    fi

    # 合并默认保护分支(去重)
    for branch in "${DEFAULT_PROTECTED[@]}"; do
        if [[ ! " ${protected_branches[@]} " =~ " $branch " ]]; then
            protected_branches+=("$branch")
        fi
    done
}

# 列出保护分支
list_protected_branches() {
    # 读取配置文件内容(不合并默认值)
    declare -a config_branches=()
    if [ -f "$CONFIG_FILE" ]; then
        while IFS= read -r line; do
            line=${line%%#*}
            line=${line// /}
            [[ -n "$line" ]] && config_branches+=("$line")
        done < "$CONFIG_FILE"
    fi

    echo "当前受保护分支列表:"
    echo "----------------------------------------"
    printf "%-25s %s\n" "分支名称" "来源"
    echo "----------------------------------------"

    # 显示配置文件中的分支
    for branch in "${config_branches[@]}"; do
        printf "%-25s %s\n" "$branch" "[配置文件] $CONFIG_FILE"
    done

    # 显示未在配置文件中出现的默认分支
    for default_branch in "${DEFAULT_PROTECTED[@]}"; do
        if [[ ! " ${config_branches[@]} " =~ " $default_branch " ]]; then
            printf "%-25s %s\n" "$default_branch" "[系统默认]"
        fi
    done

    echo "----------------------------------------"
    echo "(配置文件路径: $CONFIG_FILE)"
    exit 0
}

# 解析命令行参数
while [[ $# -gt 0 ]]; do
    case $1 in
        -d|--days)
        days_threshold="$2"
        shift; shift ;;
        -c|--config)
        CONFIG_FILE="$2"
        shift; shift ;;
        -l|--list)
        list_protected_branches ;;
        -h|--help)
        echo "用法: $0 [选项]"
        echo "选项:"
        echo "  -d, --days N     设置过期天数阈值(默认:30)"
        echo "  -c, --config FILE指定配置文件路径(默认:${CONFIG_FILE})"
        echo "  -l, --list       列出所有受保护分支"
        echo "  -h, --help       显示帮助信息"
        exit 0 ;;
        *)
        echo "未知选项: $1"; exit 1 ;;
    esac
done

# 验证天数参数
[[ "$days_threshold" =~ ^[0-9]+$ ]] || { echo "错误:天数参数必须为整数"; exit 1; }

# 加载保护分支列表
load_protected_branches

# 计算截止时间戳
cutoff=$(date -d "$days_threshold days ago" +%s)

# 获取分支信息(含创建者)
declare -a branches_info
while read ts branch; do
    branch_name=${branch#$remote/}

    # 跳过受保护分支
    [[ " ${protected_branches[@]} " =~ " $branch_name " ]] && continue

    # 获取分支创建者信息
    creator=$(git log --reverse --format="%an <%ae>" "$branch" -1 2>/dev/null || echo "未知")
    creator=${creator//|/-}  # 处理特殊字符

    # 存储分支信息:时间戳|分支名|创建者
    branches_info+=("$ts|$branch_name|${creator//$'\n'/}")
done < <(git fetch -pq && git for-each-ref --sort=committerdate \
         --format='%(committerdate:unix) %(refname:short)' \
         "refs/remotes/$remote")

# 筛选待删除分支
branches_to_delete=()
for entry in "${branches_info[@]}"; do
    IFS='|' read ts branch_name creator <<< "$entry"
    [ $ts -lt $cutoff ] && branches_to_delete+=("$branch_name|$creator")
done

# 显示结果
if [ ${#branches_to_delete[@]} -eq 0 ]; then
    echo "没有需要清理的分支(阈值:${days_threshold}天)"
    exit 0
fi

echo "以下分支超过 ${days_threshold} 天未更新(共 ${#branches_to_delete[@]} 个):"
printf "%-30s %s\n" "分支名称" "创建者"
echo "------------------------------------------------"
for entry in "${branches_to_delete[@]}"; do
    IFS='|' read branch creator <<< "$entry"
    printf "%-30s %s\n" "$branch" "$creator"
done

# 用户确认
read -p "是否确认删除这些分支?[y/N] " -n 1 -r
echo
[[ ! $REPLY =~ ^[Yy]$ ]] && { echo "操作已取消"; exit 1; }

# 执行删除
deleted_count=0
for entry in "${branches_to_delete[@]}"; do
    IFS='|' read branch_name creator <<< "$entry"

    # 删除远程分支
    if git push -q "$remote" --delete "$branch_name"; then
        echo "已删除远程分支: $branch_name (创建者: $creator)"
        ((deleted_count++))
    else
        echo "删除远程分支失败: $branch_name" >&2
        continue
    fi

    # 删除本地分支
    if [ "$delete_local" = true ]; then
        git branch -D "$branch_name" >/dev/null 2>&1 && \
        echo "已删除本地分支: $branch_name" || \
        echo "本地分支不存在: $branch_name"
    fi
done

echo "操作完成,共删除 $deleted_count 个远程分支"

脚本install-app-as-service.sh

#!/bin/bash

# 检查参数是否正确
if [ $# -ne 1 ]; then
    echo "Usage: $0 <target-directory or executable-file>"
    exit 1
fi

TARGET="$1"

# 根据输入参数的类型选择操作模式
if [ -f "$TARGET" ] && [ -x "$TARGET" ]; then
    # 单个可执行文件模式
    echo "Single executable file detected. Installing as a service."
    
    # 获取基本信息
    executable="$TARGET"
    service_name=$(basename "$executable").service
    service_path="/etc/systemd/system/$service_name"
    exec_path=$(realpath "$executable")
    work_dir=$(dirname "$exec_path")

    echo "Installing $exec_path as systemd service: $service_name"

    # 生成服务文件内容(包含路径转义)
    service_content="[Unit]
Description=Service for $(basename "$executable")
After=network.target

[Service]
Type=simple
User=root
WorkingDirectory=$work_dir
ExecStart=\"$exec_path\"
Restart=on-failure
RestartSec=5s

[Install]
WantedBy=multi-user.target"

    # 写入系统服务文件
    echo "$service_content" | sudo tee "$service_path" > /dev/null

    # 设置文件权限
    sudo chmod 644 "$service_path"

    # 重新加载systemd并启用服务
    sudo systemctl daemon-reload
    sudo systemctl enable "$service_name"
    sudo systemctl start "$service_name"

    echo "Successfully installed and started $service_name"

elif [ -d "$TARGET" ]; then
    # 目录模式,与原来功能一致
    echo "Directory mode: Installing all executables in directory as services."
    
    # 查找所有可执行文件并处理
    find "$TARGET" -maxdepth 1 -type f -executable | while read -r executable; do
        # 获取基本信息
        service_name=$(basename "$executable").service
        service_path="/etc/systemd/system/$service_name"
        exec_path=$(realpath "$executable")
        work_dir=$(dirname "$exec_path")

        echo "Installing $exec_path as systemd service: $service_name"

        # 生成服务文件内容(包含路径转义)
        service_content="[Unit]
Description=Service for $(basename "$executable")
After=network.target

[Service]
Type=simple
User=root
WorkingDirectory=$work_dir
ExecStart=\"$exec_path\"
Restart=on-failure
RestartSec=5s

[Install]
WantedBy=multi-user.target"

        # 写入系统服务文件
        echo "$service_content" | sudo tee "$service_path" > /dev/null

        # 设置文件权限
        sudo chmod 644 "$service_path"

        # 重新加载systemd并启用服务
        sudo systemctl daemon-reload
        sudo systemctl enable "$service_name"
        sudo systemctl start "$service_name"

        echo "Successfully installed and started $service_name"
    done

    echo "All executable services installed from $TARGET"
else
    echo "Error: '$TARGET' is neither a valid directory nor an executable file."
    exit 1
fi

使用:

sudo install-app-as-service.sh path-to-the-app(指向单个可执行程序)
sudo install-app-as-service.sh path-to-the-dir(指向可执行程序上级目录,支持多个批量安装)