本文记录使用命令 OHOS_ARCH=aarch64 OHOS_ABI=arm64-v8a sh ./create-hnp.sh 构建 Fish Shell 3.7.1 的完整过程,包括环境、构建链路、关键日志、常见问题与解决方案、产物验证与重建方法,便于复现与运维。

📖 Fish 简介

Fish(Friendly Interactive Shell)是一个用户友好的命令行 shell,专为交互式使用而设计。Fish 提供了开箱即用的语法高亮、自动补全、智能提示等功能,无需复杂配置即可获得出色的用户体验。

🎯 Fish 的作用与重要性

Fish 是现代化命令行交互的核心工具,提供了:

  • 用户友好的交互式 Shell:专为提升用户体验而设计
  • 语法高亮:实时语法高亮,帮助识别命令、参数和错误
  • 自动补全:智能自动补全,基于命令历史和上下文
  • 智能提示:基于命令历史的智能提示和建议
  • 无需配置:开箱即用的优秀体验,无需复杂配置
  • 现代化语法:简洁直观的语法,易于学习和使用
  • 跨平台:支持多种操作系统和平台

🔧 Fish 核心特性

1. 语法高亮
  • 实时高亮:输入时实时显示语法高亮
  • 错误提示:无效命令和语法错误会以红色显示
  • 命令识别:有效命令以不同颜色显示
  • 参数高亮:参数和选项以不同颜色区分
2. 自动补全
  • 智能补全:基于命令历史和上下文的智能补全
  • 描述提示:补全选项显示描述信息
  • 多选支持:支持多个补全选项
  • 上下文感知:根据当前命令和位置提供相关补全
3. 命令历史
  • 增量搜索:输入时自动搜索历史命令
  • 智能排序:根据使用频率和相关性排序
  • 历史共享:多个 fish 会话共享命令历史
  • 历史过滤:支持按命令、时间等过滤历史
4. 配置与定制
  • 配置文件~/.config/fish/config.fish - 用户配置文件
  • 函数目录~/.config/fish/functions/ - 自定义函数
  • 补全目录~/.config/fish/completions/ - 自定义补全
  • 主题定制:支持丰富的提示符主题
5. 辅助工具
  • fish_indent:Fish 脚本格式化工具
  • fish_key_reader:键盘输入测试工具
  • fish_config:交互式配置工具(Web 界面)

🚀 构建入口与环境

  • 📝 执行命令OHOS_ARCH=aarch64 OHOS_ABI=arm64-v8a sh ./create-hnp.sh
  • 🔧 入口脚本create-hnp.sh
    • 检查必需的环境变量 OHOS_ARCHOHOS_ABI
    • 导出 LC_CTYPETOOL_HOMEOHOS_SDK_HOME
    • 执行 make -C build-hnp
  • 📦 顶层构建build-hnp/Makefile
    • PKGS 变量定义需要构建的包列表(包含 fish
    • 通过 check-pkgs 机制自动检测 PKGS 变化并触发重新构建
    • 自动合并 external-hnp 目录下的外部 HNP 包
    • base.hnp 依赖所有包的 .stamp 和外部 HNP 包
    • 总目标 all: copy,打包 base.hnp 并拷贝到 entry/hnp/$(OHOS_ABI)

⚙️ Fish 包的构建配置

  • 📁 包目录build-hnp/fish/Makefile
    • 继承通用规则:include ../utils/Makefrag
    • 源地址:https://github.com/fish-shell/fish-shell/releases/download/3.7.1/fish-3.7.1.tar.xz
    • 版本:3.7.1
  • 🔧 补丁应用
    • 终端属性替换:将源码中的 TCSANOW 替换为 TCSADRAIN
      • src/reader.cppsrc/fish_key_reader.cppsrc/builtins/fg.cpp
      • 规避在 OHOS 环境下终端属性立即生效可能导致的异常
    • CMake 修复:应用 0001-fix-cmake.diff 修复 CMake 配置问题
    • getpwuid 适配:应用 0002-fix-getpwuid.diff 修复 getpwuid 相关问题
  • ⚙️ CMake 配置参数
    • -DSYS_PCRE2_LIB="$(shell pwd)/../sysroot/lib/libpcre2-32.so" - PCRE2 32位库路径(fish 需要32位版本处理宽字符)
    • -DSYS_PCRE2_INCLUDE_DIR="$(shell pwd)/../sysroot/include" - PCRE2 头文件路径
    • -DCMAKE_DISABLE_FIND_PACKAGE_Curses=ON - 禁用 Curses 自动查找
    • -DCURSES_FOUND=ON - 手动指定 Curses 已找到
    • -DCURSES_INCLUDE_DIRS="$(shell pwd)/../sysroot/include;$(shell pwd)/../sysroot/include/ncursesw" - Curses 头文件路径
    • -DCURSES_LIBRARY=ncursesw - Curses 库名称
    • -DCURSES_TINFO="$(shell pwd)/../sysroot/lib/libtinfow.so" - Tinfo 库路径
    • -DCMAKE_EXE_LINKER_FLAGS="-L $(shell pwd)/../sysroot/lib -lpcre2-32" - 链接标志,添加 -lpcre2-32
    • -DCMAKE_CXX_FLAGS="-DTPUTS_USES_INT_ARG=1" - C++ 编译标志
    • -DFISH_USE_SYSTEM_PCRE2=ON - 使用系统 PCRE2
    • -DWITH_GETTEXT=OFF - 禁用 gettext
    • -DBUILD_DOCS=OFF - 禁用文档构建
  • 🔨 构建流程
    1. 下载源码包(从 GitHub releases)
    2. 解包并进入 temp/fish-3.7.1 目录
    3. 应用补丁(TCSANOW→TCSADRAIN、CMake 修复、getpwuid 适配)
    4. 运行 cmake 配置构建系统
    5. 使用 make -j $(nproc) 并行编译
    6. 使用 make install 安装
    7. 删除导致 HAP 安装失败的特殊文件(..fish
    8. 复制到 ../sysroot 并 strip ELF 文件
  • 🔧 通用工具链与路径build-hnp/utils/Makefrag
    • CC/CXX/LD/AR/RANLIB/... 均指向 OHOS SDK 的 LLVM 工具链
    • 下载支持多镜像回退:wgetcurl,主镜像失败时自动尝试备用镜像

📋 关键日志与过程节点

  • 📥 下载与解包
    • 从 GitHub Releases 下载 fish-3.7.1.tar.xz
    • 完成解包并进入 temp/fish-3.7.1 目录
    • 下载规则支持多镜像回退:wgetcurl 兜底
  • 🔧 补丁应用
    • 使用 sedTCSANOW, 替换为 TCSADRAIN,(3个文件)
    • 应用 0001-fix-cmake.diff 修复 CMake 配置
    • 应用 0002-fix-getpwuid.diff 修复 getpwuid 问题
  • ⚙️ CMake 配置阶段
    • CMAKE_SYSTEM_NAME=LinuxCMAKE_SYSTEM_PROCESSOR=aarch64
    • 编译器与链接器:检测 clang/ld.lld 成功
    • PCRE2 32位库检测:成功检测到 libpcre2-32.so
    • Curses 库检测:成功检测到 ncurseswtinfow
    • 禁用文档构建:BUILD_DOCS=OFF
  • 🔨 编译与安装
    • 生成 fishfish_indentfish_key_reader 二进制文件
    • 安装大量补全脚本到 share/fish/completions/
    • 安装大量函数脚本到 share/fish/functions/
    • 安装配置文件到 etc/fish/
    • 删除导致 HAP 安装失败的特殊文件(..fish
    • 使用 llvm-strip 剥离符号,减小文件大小
  • 📦 打包
    • 完成 base.hnp 重打包,拷贝产物到 entry/hnp/arm64-v8a/
    • Fish Shell 已成功打包到 base.hnp

✅ 产物验证

📦 检查打包文件

ls build-hnp/base.hnp  # 应存在
ls entry/hnp/arm64-v8a/*.hnp  # 应包含 base.hnp 与 base-public.hnp

🔍 检查二进制文件

# 检查 Fish 可执行文件
ls -lh build-hnp/sysroot/bin/fish*
file build-hnp/sysroot/bin/fish*

✅ 构建验证结果

  • ✅ Fish 可执行文件已安装:
    • fish (1.5M) - Fish Shell 主程序
    • fish_indent (1.5M) - Fish 脚本格式化工具
    • fish_key_reader (1.5M) - 键盘输入测试工具
  • ✅ 文件类型:ELF 64-bit LSB pie executable, ARM aarch64
  • ✅ 动态链接:dynamically linked, interpreter /lib/ld-musl-aarch64.so.1
  • ✅ 已剥离符号:stripped
  • ✅ 配置文件(位于 build-hnp/sysroot/etc/fish/):
    • config.fish - 系统级配置文件
    • completions/functions/conf.d/ 目录
  • ✅ 补全和函数脚本(位于 build-hnp/sysroot/share/fish/):
    • completions/ - 大量命令补全脚本(900+ 个文件)
    • functions/ - 大量函数脚本(200+ 个文件)
    • config.fish - 默认配置文件
  • ✅ 已打包到 base.hnp

💻 终端中执行的示例命令

🐟 Fish 基本使用

1. 启动 Fish Shell
# 启动 Fish Shell
fish

# 启动 Fish 并执行命令
fish -c "echo 'Hello, Fish!'"

# 启动 Fish 并执行脚本
fish script.fish

# 检查 Fish 版本
fish --version

image-20251126160543468

2. 语法高亮
# Fish 会自动高亮命令
ls -la          # 有效命令会高亮
invalid_cmd     # 无效命令会以红色显示

# 实时语法检查
echo "Hello"    # 正确语法
echo "Hello     # 未闭合引号会高亮显示错误

image-20251126160644805

3. 自动补全
# 输入命令时按 Tab 键自动补全
ls Desktop/   # 按 Tab 会显示所有以D开头的命令

# 补全显示描述信息
git comm        # 按 Tab 会显示 commit、commit-tree 等选项及其描述

# 多选补全
cd ~/Doc        # 按 Tab 会显示 Documents、Downloads 等选项

# 上下文感知补全
git checkout    # 按 Tab 会显示分支名和文件名的补全

image-20251126160739166

4. 命令历史
# 增量搜索历史命令
# 输入命令的前几个字符,Fish 会自动显示匹配的历史命令

# 查看命令历史
history

# 搜索历史命令
history | grep git

# 清除历史
history clear

# 历史共享
# Fish 会自动在多个会话间共享命令历史
5. 变量和环境
# 设置变量
set myvar "Hello, World!"
echo $myvar

# 设置全局变量
set -gx MYVAR "Global Variable"
echo $MYVAR

# 设置路径变量
set -gx PATH $PATH /new/path

# 列出所有变量
set

# 删除变量
set -e myvar

# 环境变量
set -gx EDITOR vim
set -gx LANG en_US.UTF-8
6. 函数定义
# 定义函数
function hello
    echo "Hello, $argv[1]!"
end

# 调用函数
hello World

# 带参数的函数
function greet
    set name $argv[1]
    echo "Hello, $name!"
end

greet Alice

# 保存函数到文件
# 函数保存在 ~/.config/fish/functions/ 目录
function hello --description "Say hello"
    echo "Hello, World!"
end

# 使用 funcsave 保存函数
funcsave hello

image-20251126160954667

7. 条件语句和循环
# if 语句
if test $status -eq 0
    echo "Command succeeded"
else
    echo "Command failed"
end

# for 循环
for i in 1 2 3 4 5
    echo "Number: $i"
end

# while 循环
set i 1
while test $i -le 5
    echo "Number: $i"
    set i (math $i + 1)
end

# switch 语句
switch $argv[1]
    case start
        echo "Starting..."
    case stop
        echo "Stopping..."
    case '*'
        echo "Unknown command"
end
8. 字符串操作
# 字符串连接
set str1 "Hello"
set str2 "World"
set combined "$str1, $str2!"
echo $combined

# 字符串长度
set str "Hello"
echo (string length $str)

# 字符串替换
set str "Hello World"
echo (string replace "World" "Fish" $str)

# 字符串分割
set str "a,b,c"
string split "," $str

# 字符串匹配
if string match -q "Hello*" "Hello World"
    echo "Matched!"
end
9. 文件操作
# 读取文件内容
cat file.txt

# 写入文件
echo "Hello" > file.txt

# 追加到文件
echo "World" >> file.txt

# 检查文件是否存在
if test -f file.txt
    echo "File exists"
end

# 检查目录是否存在
if test -d /path/to/dir
    echo "Directory exists"
end

# 列出文件
ls -la

# 查找文件
find . -name "*.fish"

# 读取文件行
for line in (cat file.txt)
    echo $line
end
10. 进程和作业管理
# 后台运行进程
sleep 10 &

# 列出作业
jobs

# 前台作业
fg %1

# 后台作业
bg %1

# 杀死作业
kill %1

# 等待进程
wait %1

# 检查进程状态
ps aux | grep fish
11. Fish 配置
# 编辑配置文件
fish_config

# 手动编辑配置
vim ~/.config/fish/config.fish

# 配置示例
# ~/.config/fish/config.fish

# 设置提示符
function fish_prompt
    echo -n (prompt_pwd) "> "
end

# 设置别名
alias ll "ls -la"
alias la "ls -A"
alias l "ls -CF"

# 设置路径
set -gx PATH $PATH ~/bin

# 设置环境变量
set -gx EDITOR vim
set -gx LANG en_US.UTF-8

# 加载函数
source ~/.config/fish/functions/myfunction.fish
12. Fish 辅助工具
# fish_indent - 格式化 Fish 脚本
fish_indent script.fish

# 格式化并输出
fish_indent script.fish > formatted.fish

# fish_key_reader - 测试键盘输入
fish_key_reader

# 测试特定键
fish_key_reader -c "Ctrl+C"

# fish_config - 交互式配置(需要 Web 浏览器)
fish_config

# 启动配置服务器
fish_config start
13. 实际应用示例
# 开发环境设置
# ~/.config/fish/config.fish
function dev_setup
    set -gx GOPATH ~/go
    set -gx PATH $PATH $GOPATH/bin
    set -gx EDITOR vim
end

# 项目导航
function proj
    switch $argv[1]
        case work
            cd ~/projects/work
        case personal
            cd ~/projects/personal
        case '*'
            echo "Unknown project"
    end
end

# Git 快捷命令
function gst
    git status
end

function gco
    git checkout $argv[1]
end

function gcm
    git commit -m $argv[1]
end

# 快速查找
function ff
    find . -name "*$argv[1]*"
end

# 快速 grep
function gg
    grep -r "$argv[1]" .
end

# 系统信息
function sysinfo
    echo "OS: "(uname -s)
    echo "Kernel: "(uname -r)
    echo "Architecture: "(uname -m)
    echo "Uptime: "(uptime)
end
14. 功能验证脚本
#!/bin/bash
# Fish Shell 工具验证脚本

FISH_BIN="build-hnp/sysroot/bin"
FISH_SHARE="build-hnp/sysroot/share/fish"

echo "=== Fish Shell 工具验证 ==="

# 检查可执行文件
echo ""
echo "=== 可执行文件验证 ==="
for exe in fish fish_indent fish_key_reader; do
    if [ -f "$FISH_BIN/$exe" ]; then
        echo "✓ $exe: 存在"
        file "$FISH_BIN/$exe"
        echo "  文件大小: $(ls -lh "$FISH_BIN/$exe" | awk '{print $5}')"
        echo "  架构信息: $(file "$FISH_BIN/$exe" | grep -o "ARM aarch64")"
        echo "  链接类型: $(file "$FISH_BIN/$exe" | grep -o "dynamically linked\|statically linked")"
    else
        echo "✗ $exe: 缺失"
    fi
done

# 检查配置文件
echo ""
echo "=== 配置文件验证 ==="
if [ -f "build-hnp/sysroot/etc/fish/config.fish" ]; then
    echo "✓ config.fish: 存在"
else
    echo "✗ config.fish: 缺失"
fi

# 检查补全脚本
echo ""
echo "=== 补全脚本验证 ==="
if [ -d "$FISH_SHARE/completions" ]; then
    count=$(find "$FISH_SHARE/completions" -type f | wc -l)
    echo "✓ completions: 存在 ($count 个文件)"
else
    echo "✗ completions: 缺失"
fi

# 检查函数脚本
echo ""
echo "=== 函数脚本验证 ==="
if [ -d "$FISH_SHARE/functions" ]; then
    count=$(find "$FISH_SHARE/functions" -type f | wc -l)
    echo "✓ functions: 存在 ($count 个文件)"
else
    echo "✗ functions: 缺失"
fi

# 测试基本功能(需要在目标设备上运行)
echo ""
echo "=== 功能测试(需要在目标设备上运行)==="
echo "启动 Fish Shell:"
echo "  $FISH_BIN/fish"
echo ""
echo "检查版本:"
echo "  $FISH_BIN/fish --version"
echo ""
echo "格式化脚本:"
echo "  $FISH_BIN/fish_indent script.fish"
echo ""
echo "测试键盘输入:"
echo "  $FISH_BIN/fish_key_reader"

🐛 构建过程遇到的问题及解决方法

❌ 问题 1:PCRE2 32位库缺失

  • 🔍 症状:链接错误,找不到 pcre2_*_32 函数
  • 🔎 原因:Fish 需要 PCRE2 32位版本处理宽字符,但默认只构建了8位版本
  • ✅ 解决方法
    • 修改 PCRE2 的构建配置,启用32位版本:--enable-pcre2-32
    • 更新 fish 的 Makefile,使用 libpcre2-32.so 而不是 libpcre2-8.so
    • 在链接标志中添加 -lpcre2-32
    • 位置:build-hnp/pcre2/Makefile:8build-hnp/fish/Makefile:15

❌ 问题 2:终端属性设置问题(TCSANOW)

  • 🔍 症状:在 OHOS 环境下终端属性立即生效可能导致异常
  • 🔎 原因TCSANOW 在 OHOS 环境下行为不一致
  • ✅ 解决方法
    • 应用补丁将所有 TCSANOW, 替换为 TCSADRAIN,
    • 涉及文件:src/reader.cppsrc/fish_key_reader.cppsrc/builtins/fg.cpp
    • 位置:build-hnp/fish/Makefile:7-9

❌ 问题 3:GitHub 下载失败

  • 🔍 症状:无法从 GitHub 下载源码包
  • 🔎 原因:网络问题或 GitHub 访问受限
  • ✅ 解决方法
    • 手动下载源码包放置到 build-hnp/fish/download/ 目录
    • 使用代理或镜像站点下载
    • 通用下载规则支持 curl 兜底
    • 位置:build-hnp/fish/Makefile:3-5

❌ 问题 4:CMake 配置失败

  • 🔍 症状:CMake 无法找到 PCRE2 或 Curses 库
  • 🔎 原因:CMake 自动查找失败或路径不正确
  • ✅ 解决方法
    • 禁用 Curses 自动查找:-DCMAKE_DISABLE_FIND_PACKAGE_Curses=ON
    • 手动指定库路径和头文件路径
    • 确保 PCRE2 32位库和 Curses 库已正确安装到 sysroot
    • 位置:build-hnp/fish/Makefile:15

❌ 问题 5:HAP 安装失败

  • 🔍 症状:HAP 安装时失败,提示特殊文件名问题
  • 🔎 原因:Fish 补全目录中存在名为 ..fish 的特殊文件
  • ✅ 解决方法
    • 在安装后删除该文件:rm -fv build$(PREFIX)/share/fish/completions/..fish
    • 位置:build-hnp/fish/Makefile:13

❌ 问题 6:getpwuid 问题

  • 🔍 症状:编译或运行时 getpwuid 相关错误
  • 🔎 原因:OHOS 环境下 getpwuid 行为不一致
  • ✅ 解决方法
    • 应用 0002-fix-getpwuid.diff 修复相关问题
    • 位置:build-hnp/fish/Makefile:11

❌ 问题 7:交叉编译配置失败

  • 🔍 症状:CMake 无法检测到交叉编译器
  • 🔎 原因:环境变量未正确设置或工具链路径不正确
  • ✅ 解决方法
    • 确保通过 create-hnp.sh 触发构建以获得完整环境变量
    • 检查 OHOS_SDK_HOME 是否正确设置
    • 手动指定编译器路径:-DCMAKE_C_COMPILER-DCMAKE_CXX_COMPILER
    • 位置:build-hnp/fish/Makefile:15

❌ 问题 8:架构不支持

  • 🔍 症状Unsupported OHOS_ARCH=
  • 🔎 原因:传入的架构不在支持列表中
  • ✅ 解决方法
    • 确保传入支持架构(aarch64x86_64
    • 位置:build-hnp/Makefile:1-49

🔄 重建与扩展

  • 🔧 重建单包

    make -C build-hnp rebuild-fish  # 触发子包重新编译并刷新 .stamp
    
  • 🧹 清理

    make -C build-hnp clean  # 清理 sysroot、所有 .stamp 和 PKGS_MARKER
    
  • 📦 扩展:Fish Shell 是现代化命令行交互的核心工具,提供了出色的用户体验

  • 🔄 自动重建机制

    • 修改 PKGS 后,check-pkgs 会自动检测变化并触发重新构建
    • 新增外部 HNP 包到 external-hnp 目录后,会自动合并到 base.hnp

💡 实践建议

  • 🔧 构建配置:确保 PCRE2 32位版本已构建,fish 需要它来处理宽字符
  • 🚀 性能优化:Fish Shell 提供了出色的交互体验和性能
  • 📦 依赖管理:确保 PCRE2 和 ncurses 正确安装
  • 🔗 配置定制:根据需求定制 ~/.config/fish/config.fish 配置文件
  • 🌐 用户体验:Fish Shell 开箱即用,无需复杂配置即可获得优秀体验

📝 结论与建议

  • ✅ 本次已在 aarch64 环境下完成 Fish Shell 3.7.1 的交叉编译与打包,可执行文件和大量补全/函数脚本已安装到 sysroot 并纳入 HNP 包。
  • 💡 为保证构建稳定
    • 使用 CMake 构建系统,配置灵活
    • 应用补丁修复 OHOS 环境下的终端属性和 getpwuid 问题
    • 确保 PCRE2 32位版本已构建(fish 需要它处理宽字符)
    • 确保通过 create-hnp.sh 触发构建以获得完整环境变量
    • 利用 check-pkgs 机制自动检测包列表变化,无需手动清理
    • Fish Shell 为命令行交互提供了现代化的用户体验
    • 常见陷阱包括 PCRE2 32位库缺失、终端属性设置问题、CMake 配置失败;当前已通过补丁和配置参数处理
    • 建议充分利用 Fish Shell 的自动补全、语法高亮和智能提示功能
    • CMake 交叉编译参数与依赖注入清晰,补丁适配终端行为差异,保证在 OHOS 环境的稳定性
    • 产物完整,上层可直接使用 sysroot/bin/fish,并复用丰富的补全与函数脚本提升交互体验

📚 以上为 Fish Shell 构建的深度解读与实践记录。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐