0

Currently, I'm working on this homework assignment:

Write a Bash script, reverse.sh, which reverses the contents of a directory passed as a parameter. Assuming /my/dir contains cache cron games lib log run tmp, your program reverse.sh /my/dir will return tmp run log lib games cron cache. Use an array and two functions: main() and reverse(). You will manually manipulate the list as we did today. DO NOT use the built-in command sort -r.

For now, I've decided to use one function to get proper output first, that's not my problem. This is my script so far:

function main(){
    p=$1
    cd $p
    reverse $p
}

function reverse(){
    local p2=$1
    local ARRAY=()
    local count=0
    for entry in $p2*
    do
     ((count++))
     ARRAY+=($entry)
    done
    while [ $count -gt -1 ]; do
     echo ${ARRAY[$count]}
     ((count--))
    done
}
main

However, I get the same output every time, no matter what directory I add as a parameter when running the script.

Todd A. Jacobs
  • 81,402
  • 15
  • 141
  • 199
witcheR
  • 89
  • 4
  • 13
  • 4
    To debug shell scripts, put `set -x` at the beginning of the script. Then you'll see all the statements as they're executed, with variables expanded. – Barmar Sep 18 '16 at 21:33
  • 2
    The variable you set is `$count`, but you're using `$countx` in the `echo` statement. Is that a copying error or in the real script? – Barmar Sep 18 '16 at 21:33
  • 2
  • It was a copying error, will update the OP. I've updated the OP as well with the new function call, but it seems like no matter what directory I pass as a parameter like so: ./reverse.sh /bin/, it will still only print out contents of the current directory. – witcheR Sep 18 '16 at 21:48
  • @MarkSetchell No, that would have to be `ls | tac | xargs` to get them back into a single line. – Todd A. Jacobs Sep 19 '16 at 03:15

3 Answers3

2

p=$1 will not work because main is not being called with any arguments (the args are not automatically passed from the command line to the function). Fix is to pass all the command line args to the function:

    main $@
kaylum
  • 13,833
  • 2
  • 22
  • 31
  • 1
    You main `main "$@"`, right? `main $@` is exactly the same as `main $*` (meaning, all the same bugs: Elements with spaces will be split into multiple elements; elements with wildcards will have those expanded if any matching files; etc). – Charles Duffy Sep 19 '16 at 00:14
1

Unshift Positional Parameters in Bash

I'm sure your homework assignment is trying to teach you something in particular, but this is taking the long way around even if you're supposed to be simulating a stack with a Bash array. In Bash, you can do this with a single local variable by leveraging positional parameters and the shift builtin. For example:

# Function to sort names by emulating unshifting onto a stack.
reverse () {
    local -a result
    for dir in "$@"; do
        result=("$1" "${result[@]}") # unshift $1 onto stack
        shift                        # remove  $1 from positional params
    done
    echo "${result[@]}"
}

reverse cache cron games lib log run tmp

tmp run log lib games cron cache

For purposes of demonstration, this function assumes it is being passed a list of names to sort; it makes no assumptions about how you collect those names, because bike-shedding. However, on systems with GNU ls, you might consider reverse $(ls -Q /path/to/dir) (do not quote the command substitution) as a pragmatic solution that will handle most common use cases.

Bike-Shedding

A professional-quality script will require a great deal more bullet-proofing than can be provided in a reasonably-sized example. Considerations include:

  • Handling spaces, newlines, and other legal (but unusual) characters in filenames.
  • Handling nullglob expansions.
  • Testing for directory attributes of globs and arguments.
  • Dealing with symlinks, absolute/relative paths, and various expansion validations.
  • Dealing with globs that exceed getconf ARG_MAX or the hardcoded value of MAX_ARG_STRLEN.
  • Et cetera, ad nauseum.

My advice is to write code that is "good enough" for your known data set, rather than trying to cope with every possible edge case. It's always important to know your data and your environment so that you don't over-engineer a problem.

Todd A. Jacobs
  • 81,402
  • 15
  • 141
  • 199
  • `result=( "$1" "${result[@]}" )`, right? Otherwise you're string-splitting `$1`. And I'd strongly suggest moving the `local` to the top of the function, and having `local -a result=( )` there. That way you have the declaration run even with a zero argument count, and won't have any outer variable named `result` in use. – Charles Duffy Sep 19 '16 at 00:15
  • @CharlesDuffy You make several good points for the general use case, but none are critical for this example. Some of the quoting is actually superfluous for this corpus, but should certainly be included for good defensive programming. With *this* corpus, not quoting `$1` works just fine and is easier to visually differentiate with syntax highlighting. As for the other points you made, there's no need to declare an indexed array here, nor is there a meaningful efficiency benefit in this case from hoisting the variable declaration out of the loop at the cost of an additional line. YMMV. – Todd A. Jacobs Sep 19 '16 at 01:59
  • If the relevant corpus is the set of possible filenames on UNIX ("content of a directory" being what we're intended to be useful for), it certainly does matter, as common filesystems allow any character other than NUL. If the single test case given in the question were all an answer needed to cover, an answer could simply be `printf '%s\n' tmp run log lib games cron cache`. – Charles Duffy Sep 19 '16 at 03:09
  • @CharlesDuffy Now you're just bike-shedding, and you know it. There are all kinds of pathological inputs that will break a given script, and the homework assignment is not about parsing `ls`, quoting, NULs, or any other color you want to paint the OP's shed. If you want to write your own answer covering quoting considerations, `realpath`, failglob, parameter counting, or any of the host of other considerations needed for a truly bulletproof script, please go right ahead. However, such an answer is unlikely to help the OP understand how to simulate stacks in Bash, but I suppose YMMV. – Todd A. Jacobs Sep 19 '16 at 03:41
  • I absolutely disagree. Getting quoting right in shell is a similar concern to not having any codepaths that will lead to a NUL pointer dereference in C -- it's a prerequisite to your code actually being *correct*, and as such is a prerequisite no matter that code's function. Code with quoting bugs is code with bugs, period -- it's code that has behaviors not given in its specification that materially alter output in a manner the spec didn't request. If a Python program had `[glob.expand(x) for x in var.split()]` everywhere the correct code would just be `var`, you'd call that a bug, right? – Charles Duffy Sep 19 '16 at 12:46
  • Arguing about what color to paint a bikeshed, at its core, is arguing about *things that don't matter*. Correct quoting matters -- we handle a huge number of questions in this tag related to folks getting it wrong, and not getting it right in questions not directly related to quoting (like this one) means we're teaching bad practices by example, making it more likely we'll get the same people back with quoting-specific questions later – Charles Duffy Sep 19 '16 at 12:52
  • This is the same reason the #bash channel finds the ABS such an objectionable resource: It only pays attention to showcasing good practices *related to whichever aspect of the language it explicitly intends to be teaching at a given moment* -- so people who don't know enough of the language to know what's actually the behavior being advised and what's something sloppy done for demonstrative purposes only end up copying the bad with the good. If you're offering a teaching resource, you're teaching every practice you use, good and bad, unless called out as the latter at the point of use. – Charles Duffy Sep 19 '16 at 12:54
  • Not following these comments exactly, but shift simply pushes result onto a stack, which is then popped off by echo? – witcheR Sep 19 '16 at 16:00
  • @abdi No. I added some comments and some additional explanation that may help. push/pop and shift/unshift are different approaches to managing a stack, and the builtin `shift` really only operates on positional parameters. `echo` just sends the output of the array to standard output; it has nothing to do with stack management. – Todd A. Jacobs Sep 19 '16 at 16:50
  • I continue to wholeheartedly disagree with your "bikeshedding" addendum. One of the cornerstones to writing robust (reliable, secure) code is to understand that you **don't** know your environment, no matter how much you think you do, and thus to code defensively. If you write code only to work in the place it's going to be deployed today, you're writing it with bugs that apply in the place it's deployed (or copied/pasted) to tomorrow, or -- worse! -- bugs that apply in the corner case an attacker is creating as part of an attack. – Charles Duffy Sep 20 '16 at 15:55
  • The worst data-loss case I was ever personally witness to [barring some cases involving hardware-level disruption] was a scenario where a piece of backup-maintenance code was written with the knowledge that a directory would only ever contain files with names matching `[0-9a-f]{24}`. A buffer overflow in a new piece of code intended to create files in that directory matching the preexisting naming convention caused random junk to be added into names; that random junk included whitespace-surrounded wildcards, and all hell broke loose, involving the deletion of multiple TB of backups. – Charles Duffy Sep 20 '16 at 15:56
  • ...which is to say: I'm continuing this thread because (paraphrased) "don't sweat the corner cases unless you specifically know that they apply" is not just bad advice, but **dangerously** bad advice. When I write a StackOverflow answer that'll misbehave (in a manner other than correctly failing with an erroroneous exit status) if a string doesn't fit in `ARG_MAX`, [I actually call that out](http://stackoverflow.com/a/39598334/14122) (this particular answer literally written just a few minutes ago) -- and if I don't, and it's pointed out as a bug, I'll **fix it** rather than getting defensive. – Charles Duffy Sep 20 '16 at 16:09
0

If your script is supposed to take /my/dir as a parameter, you would need

for entry in $p2/*

As is, it works for me with main /my/dir/. Except that the directory name is included in each of the array elements. You could fix that with basename or cd.

But if your call to main in the script is supposed to pass the command line argument, you would need to end your script

main "$1"
ysth
  • 96,171
  • 6
  • 121
  • 214