-2

我正在编写一个ksh函数(放置在.profile文件中),它将显示一个子目录菜单并允许用户选择要cd进入的一个。这是代码:

# 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

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

我错过了什么?

特此征求并欢迎提出建议。

ADVA谢谢NCE

鲍勃

4

3 回答 3

1

简短的回答:你不能那样做。不要尝试。请参阅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"
}
于 2015-07-27T19:16:45.370 回答
0

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

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

  2. 所有包含 SINGLE QUOTE 字符的子目录名称都使用 BACKSLASH 字符对该字符进行转义。

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

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

  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 向导的知识帮助。我不能感谢你。

于 2015-08-01T20:14:00.230 回答
0

尽管仍然无法正常工作,但此版本确实通过了 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  }

一旦我将您的建议合并到代码中并使其正常运行,我将在此处发布它,并将我的问题标记为已回答。感谢您的友好帮助。

于 2015-07-29T16:32:54.077 回答