AskOverflow.Dev

AskOverflow.Dev Logo AskOverflow.Dev Logo

AskOverflow.Dev Navigation

  • 主页
  • 系统&网络
  • Ubuntu
  • Unix
  • DBA
  • Computer
  • Coding
  • LangChain

Mobile menu

Close
  • 主页
  • 系统&网络
    • 最新
    • 热门
    • 标签
  • Ubuntu
    • 最新
    • 热门
    • 标签
  • Unix
    • 最新
    • 标签
  • DBA
    • 最新
    • 标签
  • Computer
    • 最新
    • 标签
  • Coding
    • 最新
    • 标签
主页 / unix / 问题 / 792673
Accepted
hollowillow
hollowillow
Asked: 2025-03-19 06:09:04 +0800 CST2025-03-19 06:09:04 +0800 CST 2025-03-19 06:09:04 +0800 CST

需要 shell 脚本帮助 - 多次处理相同选项

  • 772

所以我遇到了一点障碍,我的脚本中有一个选项,它调用一个函数,允许我指定一个文件/目录,然后我想将该输出解析为菜单工具(在本例中使用 dmenu)以选择哪个文件是我特别想要的,并继续在同一脚本中将该选择作为变量使用。如果只有一个文件或目录,这种方法可以正常工作,但我希望能够多次使用该选项,然后一次性将所有输出解析为 dmenu。这是一个片段

fileSelection () {
    if [ -d "${OPTARG}" ]; then find "${OPTARG}" -type f; fi;
    if [ -f "${OPTARG}" ]; then printf '%s\n' "${OPTARG}"; fi;
}

while getopts "f:" option; do
   case "${option}" in
       f) file="$(fileSelection|dmenu)";;
   esac
done

就像我说的,如果我这样做,这会有效:

myscript -f file

或者

myscript -f directory

但我希望也能做到这一点:

myscript -f file1 -f file2

问题是,由于该函数是连续调用的,我无法像那样将输出解析到 dmenu,因为它不使用选项 file1 和 file2 调用 dmenu,而是先使用 file1,然后使用 file2,我希望这有意义。

我可能遗漏了一些非常简单的解决方案,我曾考虑过将输出简单地写入文件,然后解析可能有效的文件,但如果可能的话,我想避免通过管道传输到文件。我也试图使其符合 POSIX 标准,并希望得到后续的答案。

shell-script
  • 4 4 个回答
  • 106 Views

4 个回答

  • Voted
  1. Best Answer
    Chris Davies
    2025-03-19T07:08:02+08:002025-03-19T07:08:02+08:00

    您可以收集从您的派生的值-f并将它们全部传递给dmenu。这是一个适用于大多数目录和文件的示例 - 但不适用于包含嵌入换行符的目录和文件:

    #!/bin/bash
    #
    if ! type dmenu >/dev/null 2>&1
    then
        # Demonstration code in case dmenu is not installed
        dmenu() {
            echo "This is a fake implementation of dmenu" >&2
            sed 's/^/>   /' >&2
        }
    fi
    
    
    # List of files from the -f option
    files=()
    
    # Loop across the command line
    while getopts "f:" OPT
    do
       case $OPT in
           f)
                # Disable globbing
                OIFS="$IFS" IFS=$'\n'
                noglob=$(set -o | awk '$1=="noglob"{print $2}')
                set -o noglob
    
                # Capture the specified set of files
                files+=( $(find "$OPTARG" -type f) )
    
                # Reenable globbing
                IFS="$OIFS"
                [ "$noglob" = 'off' ] && set +o noglob
                ;;
       esac
    done
    
    # If we have some files then pass them to dmenu
    [ "${#files[@]}" -gt 0 ] && printf '%s\n' "${files[@]}" | dmenu
    

    由于您需要 POSIX 解决方案,并且您已经假设文件名不能包含换行符,因此您可以构建一个以换行符结尾的值的字符串并使用它。实际上,在这种情况下,我可能会构建一组起点,然后find | dmenu在它们之间使用,而不是像我在第一种方法中那样构建一组结果。

    #!/bin/sh
    
    # List of source files from the -f option
    files=
    
    # Loop across the command line
    nl='
    '
    while getopts "f:" OPT
    do
       case $OPT in
           f)
                # Capture the source file name
                files="$files$OPTARG$nl"
                ;;
       esac
    done
    
    # If we have some files then pass them to dmenu
    if [ -n "$files" ]
    then
        while [ -n "$files" ]
        do
            file=${files%%$nl*}     # get first NL-terminated entry
            files=${files#*$nl}     # strip it off the list
            find "$file" -type f    # generate the set of files
        done |
            dmenu
    fi
    
    • 1
  2. Stéphane Chazelas
    2025-03-19T17:08:21+08:002025-03-19T17:08:21+08:00

    您fileSelection|dmenu假设的文件路径无论如何都不包含换行符,因此您可以创建$file一个换行符分隔/定界的列表:

    NL='
    '
    files=
    ...
      (f) files=$files$(fileSelection|dmenu)$NL;;
    ...
    
    # to use $files, like pass each file as separate arguments to a
    # command, use split+glob with glob disabled
    
    IFS=$NL; set -o noglob
    cmd -- $files
    

    另一种方法是生成set -- ...shell 代码,将位置参数列表设置为文件列表

    sh_quote() {
      LC_ALL=C sed "s/'/'\\\\''/g; 1s/^/'/; \$s/\$/'/"
    }
    
    files=
    ...
      (f) files="$files $(fileSelection|dmenu|sh_quote)";;
    ...
    
    # and to use either
    eval "cmd -- $files"
    
    # or
    eval "set -- $files"
    cmd -- "$@"
    

    从 POSIX 角度来看,这仍然很危险,因为sed不需要正确处理长度超过 LINE_MAX 字节的行,并且find不能保证输出的行尽可能短(甚至不短于 PATH_MAX 本身,也不保证比 LINE_MAX 更短)。

    • 0
  3. Kusalananda
    2025-03-19T19:08:02+08:002025-03-19T19:08:02+08:00

    使选项解析while-loop 输出找到的名称,并将其输出传递给dmenu。即,将管道dmenu从输出移至 -loopfileSelection的输出while。

    -f为了正确执行此操作,我们首先需要确定命令行上至少有一个选项。这意味着必须解析选项两次:一次处理任何其他选项并检测-f,另一次对选项采取行动-f并获取文件列表并让用户选择文件名放入我们的file变量中。

    #!/bin/sh
    
    usage () {
            echo 'use with "-h" or "-f somepath" (optionally repeated)'
    }
    
    fileSelection () {
        if [ -d "${OPTARG}" ]; then find "${OPTARG}" -type f; fi;
        if [ -f "${OPTARG}" ]; then printf '%s\n' "${OPTARG}"; fi;
    }
    
    # Parse command line options and set a flag
    # if we have any -f options to deal with.
    f_flag=false
    while getopts hf: opt; do
            case $opt in
                    h) usage; exit ;;
                    f) f_flag=true ;;
                    *) echo 'error' >&2; exit 1
            esac
    done
    
    if "$f_flag"; then
            # Now parse all options again,
            # but only care about the -f options.
            OPTIND=1
    
            file=$(
                    while getopts hf: opt; do
                            case $opt in (f) fileSelection; esac
                    done | dmenu
            )
    fi
    
    • 0
  4. hollowillow
    2025-03-19T16:40:50+08:002025-03-19T16:40:50+08:00

    在进一步研究了 POSIX 数组处理方式后,我可能找到了解决方案

    #!/bin/sh
    
    fileSelection () {
        printf '%s\n' "${OPTARG}"
    }
    
    while getopts "f:" option; do
    case "${option}" in
        f) set -- "$@" "$(fileSelection)";;
    esac
    done
    
    printf '%s\n' "$@" | sed "/^-/ d;"
    

    这是脚本的一个非常简化的版本,但只需从 sed 解析到 dmenu 就可以得到我想要的结果。我回家后会再检查一下。感谢 Chris Davies 为我指明了正确的方向。

    • -1

相关问题

  • 在awk中的两行之间减去相同的列

  • 打印文件行及其长度的脚本[关闭]

  • 通过命令的标准输出以编程方式导出环境变量[重复]

  • 按分隔符拆分并连接字符串问题

  • MySQL Select with function IN () with bash array

Sidebar

Stats

  • 问题 205573
  • 回答 270741
  • 最佳答案 135370
  • 用户 68524
  • 热门
  • 回答
  • Marko Smith

    模块 i915 可能缺少固件 /lib/firmware/i915/*

    • 3 个回答
  • Marko Smith

    无法获取 jessie backports 存储库

    • 4 个回答
  • Marko Smith

    如何将 GPG 私钥和公钥导出到文件

    • 4 个回答
  • Marko Smith

    我们如何运行存储在变量中的命令?

    • 5 个回答
  • Marko Smith

    如何配置 systemd-resolved 和 systemd-networkd 以使用本地 DNS 服务器来解析本地域和远程 DNS 服务器来解析远程域?

    • 3 个回答
  • Marko Smith

    dist-upgrade 后 Kali Linux 中的 apt-get update 错误 [重复]

    • 2 个回答
  • Marko Smith

    如何从 systemctl 服务日志中查看最新的 x 行

    • 5 个回答
  • Marko Smith

    Nano - 跳转到文件末尾

    • 8 个回答
  • Marko Smith

    grub 错误:你需要先加载内核

    • 4 个回答
  • Marko Smith

    如何下载软件包而不是使用 apt-get 命令安装它?

    • 7 个回答
  • Martin Hope
    user12345 无法获取 jessie backports 存储库 2019-03-27 04:39:28 +0800 CST
  • Martin Hope
    Carl 为什么大多数 systemd 示例都包含 WantedBy=multi-user.target? 2019-03-15 11:49:25 +0800 CST
  • Martin Hope
    rocky 如何将 GPG 私钥和公钥导出到文件 2018-11-16 05:36:15 +0800 CST
  • Martin Hope
    Evan Carroll systemctl 状态显示:“状态:降级” 2018-06-03 18:48:17 +0800 CST
  • Martin Hope
    Tim 我们如何运行存储在变量中的命令? 2018-05-21 04:46:29 +0800 CST
  • Martin Hope
    Ankur S 为什么 /dev/null 是一个文件?为什么它的功能不作为一个简单的程序来实现? 2018-04-17 07:28:04 +0800 CST
  • Martin Hope
    user3191334 如何从 systemctl 服务日志中查看最新的 x 行 2018-02-07 00:14:16 +0800 CST
  • Martin Hope
    Marko Pacak Nano - 跳转到文件末尾 2018-02-01 01:53:03 +0800 CST
  • Martin Hope
    Kidburla 为什么真假这么大? 2018-01-26 12:14:47 +0800 CST
  • Martin Hope
    Christos Baziotis 在一个巨大的(70GB)、一行、文本文件中替换字符串 2017-12-30 06:58:33 +0800 CST

热门标签

linux bash debian shell-script text-processing ubuntu centos shell awk ssh

Explore

  • 主页
  • 问题
    • 最新
    • 热门
  • 标签
  • 帮助

Footer

AskOverflow.Dev

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

Language

  • Pt
  • Server
  • Unix

© 2023 AskOverflow.DEV All Rights Reserve