前言
有时候部署环境,需要配置文件路径或者载入相应的配置文件。这种情况下,如果用传统的手动修改配置的方式配置,会比较容易出意外(比如中文符号和英文符号混淆、大小写错误、l和i混淆等)。
这个时候,我解决的方式就是模仿Windows下常见的文件对话框,在下载文件、打开文件时Windows常见的文件对话框。与Windows不同的是,我要在命令行界面下使用图形化的对话框。写库是不可能写库的,我着急着用这个功能,所以就直接用了现成的dialog工具来完成。
最终解决方案
代码
因为我是在部署环境的场景下使用,所以就不用C或C++写这个代码了,使用bash代码。依赖dialog,可以使用sudo yum install dialog或sudo apt-get install dialog安装dialog。
#!/bin/bash
ERROR_CD=1 # 文件夹无法进入
ENTER_LABEL="选择文件/进入文件夹"
DONE_LABEL="选择当前文件夹"
TITLE="浏览"
TIP="当前路径: "
tmpfile=$(mktemp)
function show()
{
local init_dir=$(realpath "$1")
if ! cd "$init_dir"; then
return $ERROR_CD
fi
local list=$(ls -aQ) # -a 显示隐藏文件, -Q 每个文件左右两边添加双引号并转义特殊字符
local old_IFS=$IFS
IFS=$'\n'
listarr=($list) # 按换行符分割
IFS=$old_IFS
list=''
for i in ${!listarr[*]}; do
# 每一个文件, 添加修改日期和文件类型信息到右边
list="$list ${listarr[$i]} \"$(eval "date -d @\$(stat ${listarr[$i]} -c %Y) '+%Y-%m-%d %T'") $(eval "stat ${listarr[$i]} -c %F")\""
done
eval "dialog --ok-label '$ENTER_LABEL' --cancel-label '$DONE_LABEL' --title '$TITLE' --menu '${TIP}${init_dir}' 0 0 0 $list 2>'$tmpfile'"
return 0
}
function next_action()
{
local item=$1
if [ "$item" == "" ]; then
# 选择了当前文件夹, 直接输出当前路径到缓存文件
echo "$(pwd)" >"$tmpfile"
return 0
fi
local typ=$(ls -ld "$item" | cut -c 1)
if [ "$typ" == "d" ]; then
# 是目录, 进入
browse_dir "$item"
elif [ "$typ" == "l" ]; then
# 是符号链接, 判断链接到的文件是目录还是其他
local lnk=$(readlink "$item")
if ! cd "$(dirname "$lnk")"; then
return $ERROR_CD
fi
next_action "$(basename "$lnk")"
else
# 其他当普通文件处理, 文件名输出到缓存文件
echo "$(realpath "$item")" >"$tmpfile"
fi
}
function browse_dir()
{
local status
local init_dir=$1
show "$init_dir"
status=$?
if [ $status -ne 0 ]; then
return $status
fi
local item=$(cat "$tmpfile")
next_action "$item"
}
INIT_DIR=${1:-./}
browse_dir "$INIT_DIR"
status=$?
if [ $status -ne 0 ]; then
exit $status
fi
result=$(cat "$tmpfile")
rm -f "$tmpfile"
echo "$result" >&2
exit 0
看起来像这样

注:不同终端工具下显示的效果是不一样的。
使用方式
假设上述代码保存到browse_dir.sh,并且已经添加可执行权限。
./browse_dir.sh
如上即可显示一个文件对话框,初始路径为当前路径。
如果需要设置初始路径,可以在参数指定,如:
./browse_dir.sh /home # 初始路径设置为 /home
获取用户选择的文件/文件夹
我把用户选择放到了标准错误输出里了,也就是通过重定向标准错误输出到缓存文件,然后读取这个缓存文件就可以获取到用户的选择了。例如:
tmpfile=$(mktemp)
./browse_dir.sh / 2>$tmpfile
cat $tmpfile
rm -f $tmpfile
输出:


我怎么知道用户选择的是文件还是文件夹
要判断用户选择的是文件还是文件夹,需要自行通过命令判断,如:
ls -ld <用户选择的结果> | cut -c 1
如果输出d表示用户选择的是文件夹,输出-表示用户选择的是文件。如:

如上输出了-,所以表示是文件。

如上输出了d,所以是文件夹。
我可不可以在C等语言调用这个脚本啊
可以,调用的方法很多,如:
#include <stdio.h>
#include <stdlib.h>
int main()
{
char tmp[11] = "tmp.XXXXXX";
char cmd[512] = {0};
char item[261] = {0};
char typ;
FILE *tmpfile = NULL;
int tmpfd = mkstemp(tmp);
close(tmpfd);
sprintf(cmd, "./browse_dir.sh / 2>%s", tmp);
system(cmd);
tmpfile = fopen(tmp, "rb");
fread(item, 260, 1, tmpfile);
fclose(tmpfile);
strtok(item, "\n");
fprintf(stdout, "你选择了: %s\n", item);
fprintf(stdout, "它是一个: ");
sprintf(cmd, "ls -ld %s | cut -c 1 >%s", item, tmp);
system(cmd);
tmpfile = fopen(tmp, "rb");
fread(&typ, 1, 1, tmpfile);
fclose(tmpfile);
if (typ == 'd')
{
fprintf(stdout, "文件夹\n");
}
else
{
fprintf(stdout, "文件\n");
}
remove(tmp);
return 0;
}
如果想把脚本直接放到C代码里也可以这样写:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
char bash_script_data[4096];
char *browse_dir(const char *init_dir, const char *output)
{
strcpy(bash_script_data, "#!/bin/bash\n"
"ERROR_CD=1\n"
"ENTER_LABEL=\"选择文件/进入文件夹\"\n"
"DONE_LABEL=\"选择当前文件夹\"\n"
"TITLE=\"浏览\"\n"
"TIP=\"当前路径: \"\n"
"savedir=$(pwd)\n"
"tmpfile=$(mktemp)\n"
"function show()\n"
"{\n"
" local init_dir=$(realpath \"$1\")\n"
" if ! cd \"$init_dir\"; then\n"
" return $ERROR_CD\n"
" fi\n"
" local list=$(ls -aQ)\n"
" local old_IFS=$IFS\n"
" IFS=$'\\n'\n"
" listarr=($list)\n"
" IFS=$old_IFS\n"
" list=''\n"
" for i in ${!listarr[*]}; do\n"
" list=\"$list ${listarr[$i]} \\\"$(eval \"date -d @\\$(stat ${listarr[$i]} -c %Y) '+%Y-%m-%d %T'\") $(eval \"stat ${listarr[$i]} -c %F\")\\\"\"\n"
" done\n"
" eval \"dialog --ok-label '$ENTER_LABEL' --cancel-label '$DONE_LABEL' --title '$TITLE' --menu '${TIP}${init_dir}' 0 0 0 $list 2>'$tmpfile'\"\n"
" return 0\n"
"}\n"
"function next_action()\n"
"{\n"
" local item=$1\n"
" if [ \"$item\" == \"\" ]; then\n"
" echo \"$(pwd)\" >\"$tmpfile\"\n"
" return 0\n"
" fi\n"
" local typ=$(ls -ld \"$item\" | cut -c 1)\n"
" if [ \"$typ\" == \"d\" ]; then\n"
" browse_dir \"$item\"\n"
" elif [ \"$typ\" == \"l\" ]; then\n"
" local lnk=$(readlink \"$item\")\n"
" if ! cd \"$(dirname \"$lnk\")\"; then\n"
" return $ERROR_CD\n"
" fi\n"
" next_action \"$(basename \"$lnk\")\"\n"
" else\n"
" echo \"$(realpath \"$item\")\" >\"$tmpfile\"\n"
" fi\n"
"}\n"
"function browse_dir()\n"
"{\n"
" local status\n"
" local init_dir=$1\n"
" show \"$init_dir\"\n"
" status=$?\n"
" if [ $status -ne 0 ]; then\n"
" return $status\n"
" fi\n"
" local item=$(cat \"$tmpfile\")\n"
" next_action \"$item\"\n"
"}\n"
"INIT_DIR=");
strcat(bash_script_data, init_dir);
strcat(bash_script_data, "\n"
"browse_dir \"$INIT_DIR\"\n"
"status=$?\n"
"if [ $status -ne 0 ]; then\n"
" exit $status\n"
"fi\n"
"result=$(cat \"$tmpfile\")\n"
"rm -f \"$tmpfile\"\n"
"cd \"$savedir\"\n"
"echo \"$result\" >");
strcat(bash_script_data, output);
strcat(bash_script_data, "\n");
return bash_script_data;
}
int main()
{
char tmp[11] = "tmp.XXXXXX";
char cmd[512] = {0};
char item[261] = {0};
char typ;
FILE *tmpfile = NULL;
int tmpfd = mkstemp(tmp);
close(tmpfd);
system(browse_dir("/", tmp));
tmpfile = fopen(tmp, "rb");
fread(item, 260, 1, tmpfile);
fclose(tmpfile);
strtok(item, "\n");
fprintf(stdout, "你选择了: %s\n", item);
fprintf(stdout, "它是一个: ");
sprintf(cmd, "ls -ld %s | cut -c 1 >%s", item, tmp);
system(cmd);
tmpfile = fopen(tmp, "rb");
fread(&typ, 1, 1, tmpfile);
fclose(tmpfile);
if (typ == 'd')
{
fprintf(stdout, "文件夹\n");
}
else
{
fprintf(stdout, "文件\n");
}
remove(tmp);
return 0;
}
输出:

本文介绍了如何在Linux命令行环境下使用bash脚本结合dialog工具实现一个图形化的文件或文件夹选择对话框。通过这个对话框,用户可以在终端中直观地浏览和选择文件或目录,避免手动输入带来的错误。脚本同时提供了在C等语言中调用的示例,方便集成到其他程序中。

1316

被折叠的 条评论
为什么被折叠?



