3

我正在用 bash 编写一个简单的递归 ls 程序(我对此非常没有经验,所以随意残酷)。

该程序应该在单独的行上打印出每个文件(可能是目录),并且每次进入一个新目录时,输出都会移动 4 个空格,以提供树状输出。

目前,它不会正确打印带空格的文件,并且不会在目录后放置正斜杠。(下面有更多详细信息。)

代码

recls () {

    # store current working directory
    # issues: seems bad to have cwd defined up here and used down below in getAbsolutePath -- too much coupling
    cwd=$PWD
    # get absolute path of arg
    argdir=`getAbsolutePath "$@"`
    # check if it exists
    if [ ! -e $argdir ]; then
        echo "$argdir does not exist"
        return 1
    fi
    echo "$argdir exists"
    # check if it's a directory
    if [ ! -d $argdir ]; then
        echo "$argdir is not a directory"
        return 2
    fi
    echo "$argdir is a directory"
    tab=""
    recls_internal $argdir
    return 0

}

recls_internal () {

    for file in $@; do
        echo -n "$tab${file##/*/}"
        if [ -d $file ]; then
            # print forward slash to show it's a directory
            echo "/"
            savedtab=$tab
            tab="$tab    "
            myls_internal $file/*
            tab=$savedtab
        else
            # if not a directory, print a new line
            echo ""
        fi   
    done

}

getAbsolutePath () {

    if [ -z ${1##/*} ]; then
        echo "$1"
    else
        echo "$cwd/$1"
    fi

}

输出

该脚本包含在一个名为bash-practice. 当我这样做时recls .,我得到以下输出:

./
    myls.sh
    myls.sh~
    recdir.sh
    recls.sh
    recls.sh~
    sample
    document.txt
    sample-folder
        sample-stuff
            test-12.txt
        test-1.txt
        test-2.txt
        sort-test.txt
        sort-text-copy.txt
        test-5-19-14-1

问题

如您所见,缩进工作正常,但有两个问题:

1)文件sample document.txt分布在两行,因为它有一个空格。

2)每个目录前面都应该有一个正斜杠,但由于某种原因,它只适用于第一个。

尝试的解决方案

为了修复 (1),我尝试保存内部文件分隔符并将其替换为换行符,如下所示:

...
tab=""
savedIFS=$IFS
IFS="\n"
recls_internal $argdir
IFS=$savedIFS
return 0

但这根本不起作用。它甚至没有显示超过第一个文件夹。显然我对事物的理解是不正确的。

至于(2),我看不出它不应该按预期工作的任何理由。

结论

bash 对我来说很难,因为它似乎比大多数其他编程语言(作为一种 shell 脚本语言)有更多不寻常的语法,所以我希望能对我的错误有任何见解,以及解决方案。

更新#1

我去了mklement0建议的网站http://www.shellcheck.com,它的提示基本上都是双引号。当我双引号"$@"时,程序正确地打印了文件sample document.txt,但紧接着,它给了我一个“ binary operator expected”错误。这是它现在的样子的打印:

在此处输入图像描述

更新 #2 [问题解决了吗?]

好的,事实证明我有一个错字,导致它默认为myls_internal递归时调用的我的函数的早期版本。这个早期版本没有用正斜杠标记目录。“更新”部分中的错误消息也已修复。我换了行

myls_internal "$file/*"

recls_internal $file/*

现在它似乎工作正常。如果有人正在写答案,我仍然感谢您的见解,因为我并不真正了解引用“$@”如何解决间距问题的机制。

固定代码:

recls () {

    # store current working directory
    # issues: seems bad to have cwd defined up here and used down below in getAbsolutePath -- too much coupling
    cwd=$PWD
    # get absolute path of arg
    argdir=$(getAbsolutePath "$@")
    # check if it exists
    if [ ! -e $argdir ]; then
        echo "$argdir does not exist"
        return 1
    fi
    echo "$argdir exists"
    # check if it's a directory
    if [ ! -d $argdir ]; then
        echo "$argdir is not a directory"
        return 2
    fi
    echo "$argdir is a directory"
    tab=""
    recls_internal $argdir
    return 0

}

recls_internal () {

    for file in "$@"; do
        echo -n "$tab${file##/*/}"
        if [ -d "$file" ]; then
            # print forward slash to show it's a directory
            echo "/"
            savedtab=$tab
            tab="$tab    "
            recls_internal $file/*
            tab=$savedtab
        else
            # if not a directory, print a new line
            echo ""
        fi   
    done

}

getAbsolutePath () {

    if [ -z ${1##/*} ]; then
        echo "$1"
    else
        echo "$cwd/$1"
    fi

}

固定输出:

在此处输入图像描述

更新#3

线

recls_internal $file/*

应该改为

recls_internal "$file"/*

它正确处理带有空格的目录。否则,一个文件夹,如cs 350包含Homework1.pdfHomework2.pdf将展开为

cs 350/Homework1.pdf 350/Homework2.pdf

什么时候应该

cs 350/Homework1.pdf cs 350/Homework2.pdf

我认为?我并没有真正了解正在发生的事情的更详细的信息,但这似乎解决了它。

4

1 回答 1

1

为了说明 和 之间的区别"$@"$@让我们考虑以下两个函数:

f() { for i in $@; do echo $i; done; }

g() { for i in "$@"; do echo $i; done; }

当使用参数调用这些函数时,a "b c" "d e"结果将是

  • 函数 f

f a "b c" "d e" a b c d e

  • 函数 g g a "b c" "d e" a b c d e

因此,当 "$@" 在双引号内时,扩展将每个参数保留在一个单独的单词中(即使参数包含一个或多个空格)。$@(不带双引号)展开时,带空格的参数将被视为两个单词。

在您的脚本中,您还需要用双引号括起来argdirfile当目录或文件的名称包含空格时很有用,因此名称将被视为单个值。在您的脚本下方修改。

#! /bin/bash -u
recls () {

    # store current working directory
    # issues: seems bad to have cwd defined up here and used down below in getAbsolutePath -- too much coupling
    cwd=$PWD
    # get absolute path of arg
    argdir=`getAbsolutePath "$@"`
    # check if it exists
    if [ ! -e "$argdir" ]; then
        echo "$argdir does not exist"
        return 1
    fi
    echo "$argdir exists"
    # check if it's a directory
    if [ ! -d "$argdir" ]; then
        echo "$argdir is not a directory"
        return 2
    fi
    echo "$argdir is a directory"
    tab=""
    recls_internal "$argdir"
    return 0

}

recls_internal () {

    for file in "$@"; do
        echo -n "$tab${file##/*/}"
        if [ -d "$file" ]; then
            # print forward slash to show it's a directory
            echo "/"
            savedtab=$tab
            tab="$tab    "
            recls_internal "$file"/*
            tab=$savedtab
        else
            # if not a directory, print a new line
            echo ""
        fi   
    done

}

getAbsolutePath () {

    if [ -z ${1##/*} ]; then
        echo "$1"
    else
        echo "$cwd/$1"
    fi

}
于 2014-05-20T20:12:54.867 回答