【问题标题】:Setting Shell Positional Parameters With "set ls -AF"使用“set ls -AF”设置壳位置参数
【发布时间】:2015-07-27 19:12:37
【问题描述】:

我正在编写一个 ksh 函数(放置在 .profile 文件中),它将显示一个子目录菜单并允许用户选择其中的一个光盘。代码如下:

# Menu driven subdirectory descent.
d(){
# Only one command line argument accepted
[ "$1" = "--" ] && shift $# #   Trap for "ls --" feature
wd=`pwd`; arg="${1:-$wd}"
dirs="`/bin/ls -AF $arg  2>/dev/null | grep /$ | tr -d \"/\"`"
#       Set the names of the subdirectories to positional parameters
if [ "$dirs" ] ;then
        set $dirs
        if [ $# -eq 1 -a "$arg" = "$wd" ] ;then cd $arg/$1; return; fi # trap: it's obvious; do it
else echo "No subdirectories found" >&2; return 1
fi
#       Format and display the menu
if [ `basename "${arg}X"` = "${arg}X" ] ;then arg="$wd/$arg"; fi # Force absolute path if relitive
echo -e "\n\t\tSubdirectories relative to ${arg}: \n"
j=1; for i; do echo -e "$j\t$i"; j=`expr $j + 1`; done | pr -r -t -4 -e3 
echo -e "\n\t\tEnter the number of your choice -- \c "
#       Convert user-input to directory-name and cd to it
read choice; echo
dir=`eval "(echo $\{"$choice"\})"`  #       Magic here.
[ "$choice" -a "$choice" -ge 1 -a "$choice" -le "$#" ] && cd $arg/`eval echo "$dir"`
}

除了包含 空格 字符的目录名之外,此函数运行良好。如果目录名包含空格set 命令将目录名的每个空格分隔元素(而不是完整的目录名)设置为单独的位置参数;这在这里没有用。

我尝试设置 $IFS shell 变量(其中包含 空格制表符换行符 默认情况下)到单个 换行 字符:

IFS=`echo`        # echo outputs a trailing newline character by default

这似乎完成了所验证的预期目标:

echo -e "$IFS\c" | hexdump -c

但是,尽管我尽了最大努力(在几天的工作过程中),我还是未能将包含空格的整个目录名称设置为位置参数的值。

我错过了什么?

特此征求建议,非常欢迎。

ADVAthanksNCE

鲍勃

【问题讨论】:

标签: shell set ksh ifs


【解决方案1】:

简短回答:你不能那样做。不要尝试。请参阅 the ParsingLs 页面了解为什么以编程方式使用 ls 本质上容易出错。


如果不自己在 shell 中实现-F 行为,您将无法获得它(这确实可行),但以下是将子目录列表放入参数列表的正确方法:

set -- */

如果您不想在每个条目的末尾添加文字 /

set -- */       # put list of subdirectories into "$@"
set -- "${@%/}" # strip trailing / off each

不过更好的是:使用数组来避免以后需要 eval 魔法。

dirs=( */ )
dirs=( "${dirs[@]%/}" )
printf '%s\n' "${dirs[$choice]}" # emit entry at position $choice

让我们把这一切联系起来:

d() {
  destdir=$(
    FIGNORE= # ksh93 equivalent to bash shopt -s dotglob
    while :; do
      subdirs=( ~(N)*/ ) # ksh93 equivalent to subdirs=( */ ) with shopt -s nullglob
      (( ${#subdirs[@]} > 2 )) || break # . and .. are two entries

      for idx in "${!subdirs[@]}"; do
        printf '%d) %q\n' "$idx" "${subdirs[$idx]%/}" >&2
      done

      printf '\nSelect a subdirectory: ' >&2
      read -r choice
      if [[ $choice ]]; then
        cd -- "${subdirs[$choice]}" || break
      else
        break
      fi
    done
    printf '%s\n' "$PWD"
  )
  [[ $destdir ]] && cd -- "$destdir"
}

【讨论】:

  • 非常感谢您提供这项技术。效果很好!
  • 关于解析 ls,这似乎是对您提供的指针的反驳:unix.stackexchange.com/questions/128985/why-not-parse-ls>。在接受这个问题已经得到回答之前,我将发布一个包含您的建议的工作版本。现在,这里是通过 shellcheck 的新版本 d 函数的副本:
  • [很遗憾,由于堆栈交换对 cme​​ts 的字符数限制,我无法发布该代码。]
  • 您可以考虑使用相关代码发布您自己的答案。
  • 这是我第一次使用 stackexchange,我不知道该怎么做。啊,我看到了这个页面底部的框...
【解决方案2】:

虽然仍然无法正常工作,但此版本确实通过了 shellcheck,尽管有一个例外:

3  # Menu driven subdirectory descent.
4  function d{
5  # Only one command line argument accepted
6  [ "$1" = "--" ] && shift $#        # Trap for "ls --" feature
7  wd="$PWD"; arg="${1:-$wd}"
8  set -- "${@%/}"              #       Set the names of the subdirectories to positional parameters
9  if [ $# -eq 1 -a "$arg" = "$wd" ] ;then cd "$arg/$1" || exit 1; return; # trap: it's obvious; do it
10  else echo "No subdirectories found" >&2; return 1
11  fi
12  #       Format and display the menu
13  if [[ $(basename "${arg}X") = "${arg}X" ]] ;then arg="$wd/${arg}"; fi # Force absolute path if relitive
14  echo -e "\n\t\tSubdirectories relative to ${arg}: \n"
15  j=1; for i; do echo -e "$j\t$i"; j=(expr $j + 1); done | pr -r -t -4 -e3 
16  echo -e "\n\t\tEnter the number of your choice -- \c "
17  #       Convert user-input to directory-name and cd to it
18  read -r choice; echo
19  dir=(eval "(echo $\{\"$choice\"\})")        #       Magic here.
20  [ "$choice" -a "$choice" -ge 1 -a "$choice" -le "$#" ] && cd "${arg}"/"$(eval echo "${dir}")" || exit 1
                                                                                      ^SC2128 Expanding an array without an index only gives the first element.
21  }

一旦我将您的建议纳入代码并使其发挥作用,我将在此处发布,并将我的问题标记为已回答。感谢您的热心帮助。

【讨论】:

  • function 是错误的形式,在 POSIX sh 中不存在 - 请改用 d() {
  • 还有很多我直接给出的问题(现在转移到聊天)中没有在此代码中考虑。
  • (顺便说一句,我之前误会了——我以为你实际上有一个想要发布的有效答案——在建议“添加答案”按钮时)。
  • 我衷心感谢您的示例代码答案。它似乎按预期工作,但有一些例外。 1. 它在目录名称中的空格字符之前放置一个反斜杠。 2. 目录菜单垂直显示。虽然这将适应长目录名称(这很有用),但它限制了选择菜单可能包含的目录名称的数量,而无需滚动屏幕。您的代码简洁优雅,非常感谢您的睿智帮助。顺便说一句,shellcheck 在第 6 行发现:“扩展没有索引的数组只会给出第一个元素。
  • 哦,它也无法显示以点开头的目录名称。
【解决方案3】:

我已将您好心编写的代码用作下面 d 函数的基础。它几乎可以满足我的需求,但有一些小问题:

  1. 所有包含空格字符的子目录名称都被字符包围,但那些不被字符包围的。

  2. 所有包含单引号字符的子目录名称都使用反斜杠字符对该字符进行转义。

鉴于上述 12 不会引起任何问题,因此它们是可以接受的,但并不理想。

  1. cd 用户输入后,子目录名称的菜单再次循环通过。我想这可以被认为是一个功能。我尝试用 return 代替 cd 命令之后的代码部分中的 brake 命令,但未能成功克服随后的循环菜单。

  2. 包含“.”而子目录菜单开头的“..”并不理想,实际上没有什么用处。

------------------- 代码开始 -------------- ----

d() {
    if [ "$BASH" ] && [ "$BASH" != "/bin/sh" ]; then
       echo "$FUNCNAME: ksh only";return 1
    fi

    FIGNORE= # ksh93 equivalent to bash shopt -s dotglob


    if [ ${#} -gt 0 ] ;then             # Only one command line argument accepted
      cd -- "$1" && return 0
    fi

    if [ `ls -AF1|grep /|wc -l` -eq 1 ] ;then       # cd if only one subdirectory
      cd -- `ls -AF1|grep /` && return 0
    fi

  destdir=$(
    while :; do
      subdirs=( ~(N)*/ ) # ksh93 equivalent to subdirs=( */ ) with shopt -s nullglob
      (( ${#subdirs[@]} > 2 )) || break # . and .. are two entries

      echo -e "\n\t\tSubdirectories below ${PWD}: \n" >&2

      for idx in "${!subdirs[@]}"; do
        printf '%d) %q\n' "$idx" "${subdirs[$idx]%/}" >&2
      done

      printf '\nSelect a subdirectory: ' >&2
      read -r
      if [[ $REPLY ]]; then
        cd -- "${subdirs[$REPLY]}" || break     # Continue to loop through subdirectories after cding
      else
        break
      fi
    done
    printf '%s\n' "$PWD"
  )

--------------------------- 代码结束 ------------------ ------------------

所以,总的来说,我很高兴,并且认为自己很幸运能够得到这样一位有成就的 Unix 向导的知识帮助。感激不尽。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-01-21
    • 2020-01-26
    • 1970-01-01
    • 2019-06-15
    • 2018-07-24
    • 2017-05-12
    相关资源
    最近更新 更多