[Internal testing communication] 如何优雅地运行Appmage
Tofloor
poster avatar
remyxo
deepin
2025-06-30 12:39
Author
appimage可以直接运行,但是升级时文件名会变(用新版本文件替换老版本文件,一般是版本号那部分),desktop文件中的Exec部分还得跟着更改,或者将文件名改为跟desktop中一样的固定文件(例如:去掉版本号),但也得每次省级时去改文件名,太麻烦了。
我写了一个shell脚本:appimage-run,将AppImage文件传入,文件名的版本部分用“*”表示(例如:Cherry-Studio-*-x86_64.appimage):
appimage-run "/home/user_name/bin/Cherry-Studio-*-x86_64.appimage"
脚本自动判断匹配的文件名,按版本大小排序,取最高版本的那个appimage文件执行。
 
例如:当Cherry Studio升级时,会将老的appimage(例如:Cherry-Studio-1.4.5-x86_64.AppImage)删除,替换为新的(例如:Cherry-Studio-1.4.7-x86_64.AppImage),
下次启动应用时,该脚本会找到Cherry-Studio-1.4.7-x86_64.AppImage,进而运行他。
如果是手工下载了新版本的appimage文件,该脚本会找到多个不同版本的文件(1.3.5,1.4.5,1.4.7),选择版本号最高的那个(1.4.7)执行。
 
这样,就不需要经常改appimage文件名或desktop文件中的Exec字段了。
 
一、如何获取desktop文件和图标
appimage运行后,会在/tmp目录创建一个以.mount开头的临时目录,对于Cherry Studio,我机器上的路径为:/tmp/.mount_CherryjWOOAE :
目录下有desktop文件和icon文件,把desktop文件复制到~/.local/share/applications/目录下,把icon文件复制到软链接对应的/usr/share/icons/下,或者复制到自己指定的目录下(例如:~/bin/)
然后修改desktop文件(将user_name替换成你自己的用户名):
将Exec=AppRun --no-sandbox %U     更换成:Exec=/home/user_name/bin/appimage-run "Cherry-Studio-*-x86_64.AppImage" --no-sandbox %U
如果icon放到了/usr/share/icons/hicolor指定的路径下,则Icon=cherrystudio保持不变;如果icon复制到了指定的目录,如~/bin/,则Icon=一行加上相关的路径,例如:
Icon=/home/user_name/bin/cherrystudio.png
这样就大功告成啦!
 
二、shell脚本
#!/bin/bash
#
# 使用方法:appimage-run "[path/]template" [ args ]
#     例如:appimage-run "Chatbox-*-x86_64.AppImage"
#           appimage-run "/home/user_name/bin/Chatbox-*-x86_64.AppImage" --no-sendbox
#      注1:带星号的template必须用双引号括起来,避免引起通配符提前展开;
#      注2:path中不能含有~等shell特殊用途的符号,引号中路径不会自动展开;
#      注3:desktop文件中Exec路径不能用~,必须用实际路径:/home/user/。

# 全局变量/参数
APP=$(basename "$(readlink -f "$0")")
VER=1.6
MSGBOX_GUI=zenity

# void detect_gui(void);
detect_gui()
{
    if test -x "$(command -v zenity 2>/dev/null)"; then     # GNOME默认
        MSGBOX_GUI=zenity
    elif test -x "$(command -v kdialog 2>/dev/null)"; then  # KDE默认
        MSGBOX_GUI=kdialog
    elif test -x "$(command -v dialog 2>/dev/null)"; then   # 文本对话框dialog
        MSGBOX_GUI=dialog
    else   # 未安装任何对话框组件
        MSGBOX_GUI=null
    fi
}

# Display a Message Box. 
#   model for zenity : info, warning, error, question, ... 
#   model for kdialog: msgbox(info, warning), error(error), yesno(question), ... 
#   model for dialog : msgbox(info, warning, error), yesno(question), ... 
# int w_dialog(string model, string title, string text);
w_dialog()
{
    if [ $# -lt 3 ]; then return 1; fi    # same as cancel ?

    v_model="$1"
    v_title="$2"
    v_text="$3"
    shift 3

    ret=0
    case ${MSGBOX_GUI} in
        zenity)  # Debian 13 trixie need to add --no-wrap, otherwise, the message may wrap.
            case ${v_model} in
                question|yesno)
                    zenity --question --title="${v_title}" --text="${v_text}" --no-wrap "$@"
                    ret=$? ;;
                info|msgbox)
                    zenity --info --title="${v_title}" --text="${v_text}" --no-wrap "$@"
                    ret=$? ;;
                warning|error|*)
                    zenity --"${v_model}" --title="${v_title}" --text="${v_text}" --no-wrap "$@"
                    ret=$? ;;
            esac ;;
        kdialog)  # kdialog的通用选项必须在box选项前面。
            case ${v_model} in
                question|yesno)
                    kdialog --title="${v_title}" "$@" --yesno "${v_text}"
                    ret=$? ;;
                info|warning|msgbox)
                    kdialog --title="${v_title}" "$@" --msgbox "${v_text}"
                    ret=$? ;;
                error|*)
                    kdialog --title="${v_title}" "$@" --error "${v_text}"
                    ret=$? ;;
            esac ;;
        dialog)  # dialog的通用选项必须在box选项前面。(只有在命令行启动时才显示)
            case ${v_model} in  # 高度、宽度设为0:自动调整大小,设为-1:最大化对话框
                question|yesno) 
                    dialog --title "${v_title}" "$@" --yesno "${v_text}" 0 0
                    ret=$? ;;
                info|warning|error|msgbox|*)
                    dialog --title "${v_title}" "$@" --msgbox "${v_text}" 0 0
                    ret=$? ;;
            esac ;;
        null|*)  # 没有合适的对话框组件,echo回显。(只有在命令行启动时才显示)
            echo -e "+----------------------------------------------------------+"
            echo -e "|  ${v_title} "
            echo -e "+----------------------------------------------------------+"
            echo -e "${v_text}"
            echo -e "+----------------------------------------------------------+"
            ret=0 ;;
    esac
    return $ret	
}

# void w_error(string title, string text);
w_error()
{
    w_dialog "error" "$@"
}

# void w_info(string title, string text);
w_info()
{
    w_dialog "info" "$@"
}

# string get_help(void);
get_help()
{
    msg="用法:\n"
    msg+="    ${APP} \"[path/]template\" [ args ] \n\n"
    msg+="注意:\n"
    msg+="    1、\"template\"必须用双引号括起来,防止通配符被提前展开;\n"
    msg+="    2、如果\"template\"未给出路径,默认路径同${APP};\n"
    msg+="    3、引号中不能使用shell的特殊符号,如~等。"

    echo "${msg}"
}

# void usage(void);
usage()
{
    msg="AppImage Runner Utility (${APP}) v${VER}\n"
    msg+="Copyright© 2024-2025 remyxo®, remyxo2000@sina.com"
    msg+="\n\n$(get_help)"

    w_info "关于 ${APP} ..." "${msg}"
}

#############################################################################
# int main(int argc, string argv[])
#############################################################################

detect_gui

# 参数个数:
#     1:[path/]template;
#   >=2:[path/]template、应用程序需要的参数;
if [ $# -eq 0 ]; then
    usage
    exit 1
fi

# 解析参数。一个参数:"template";2个及以上参数:template、args ...
tmpl=$(basename "$1")
if [[ "$1" == */* ]]; then  # template中含有路径。
    path=$(dirname "$1")
else  # 不含路径,使用$0的路径。
    path=$(dirname "$(readlink -f "$0")")
fi
shift 1

# 再次检查参数合法性。
if [[ "${path}" == "/" ]] || [[ "${tmpl}" == "." ]]; then  # /XXXX or ./
    msg="\"[path/]template\" 格式错误!"
    msg+="\n\n$(get_help)"
    w_error "错误" "${msg}"
    exit 1
fi

# 在指定路径下查找匹配"${tmpl}"的文件或链接。
#   1) -maxdepth 1 : 只查找本目录,不查找子目录。
#   2) -V, --version-sort : 对文本中的数字(或版本号)进行自然排序。
# SC2012, use find instead of ls.
app=$(find "${path}" -maxdepth 1 -name "${tmpl}" 2>/dev/null | \
      sort --version-sort --reverse | awk '{ if(NR==1) print $0}')
if [ -z "${app}" ]; then
    msg="找不到应用程序:\"${tmpl}\" !\n"
    msg+="请确认路径 \"${path}\" 及应用 \"${tmpl}\" 是否正确。"
    msg+="\n\n$(get_help)"
    w_error "错误" "${msg}"
    exit 1
fi

# ${app}不是普通文件,或没有执行权限。(链接取决于源对象)
if [ ! -f "${app}" ] || [ ! -x "${app}" ]; then
    file=$(basename "${app}")
    msg="\"${file}\" 不是一个可执行的文件!\n所在路径:\"${path}\"。"
    msg+="\n\n$(get_help)"
    w_error "错误" "${msg}"
    exit 1
fi

# 执行应用程序${app}。
"${app}" "$@"
exit $?
 
三、注意事项
1、desktop文件中路径不能用shell特殊符号,例如~,必须用全路径,例如:/home/your_name/bin;
2、带星号的应用名必须用双引号括起来,避免shell脚本提前展开;
3、如果带星号的appimage文件名不带路径,则appimage文件所在路径同appimage-run所在路径相同。
 
四、desktop文件例子
[Desktop Entry]
Name=Cherry Studio
Exec=/home/user_name/bin/appimage-run "Cherry-Studio-*-x86_64.AppImage" %U
Icon=cherrystudio
Terminal=false
Type=Application
Categories=Development;Office;Utility;
StartupWMClass=CherryStudio
X-AppImage-Version=1.1.10
Comment=A powerful AI assistant for producer.
Reply Favorite View the author
All Replies
zccrs
deepin
2025-06-30 13:35
#1

666

Reply View the author
绿竹
deepin
2025-06-30 13:53
#2

收藏点赞,正想要获取桌面图标的方法

Reply View the author
虫二
deepin
2025-06-30 14:23
#3

我是直接appimage转deb后安装

Reply View the author
remyxo
deepin
2025-06-30 17:26
#4
虫二

我是直接appimage转deb后安装

我也想过,不过我没验证过软件升级是怎样子的,如果不能自动更新已经安装好的相关文件,还是要重新制作新的deb文件来覆盖安装,那就太麻烦了。(尤其是有些频繁更新的软件)

Reply View the author
BigFish
deepin
2025-06-30 18:26
#5

我很喜欢用Appmage,可以把程序放非系统盘

Reply View the author