2

I know that this is a ridiculous idea, but I'm trying to get a script to work that branches through every directory on the file system. The file's name is "Everywhere.sh". Here's the code:

#!/bin/bash

recurse(){
    cd $1   
    for INDEX in $(echo *)
    do
        recurse $INDEX
    done
}

recurse /

How can I change this (aside from su root -c "./Everywhere.sh") so that it works properly?

EDIT: I just need this fixed; I don't need a different method for execution.

  • 3
    You mean so it can see everything? You need to be root (su or sudo), there's no way around that, otherwise permissions would be useless. – Kevin Jan 13 '16 at 20:48
  • 2
    if it is about programming, it should go to StackOverflow.com. Also you should clarify what is "not working". – Jakuje Jan 13 '16 at 20:54
  • 3
    (a) You can't cd to non-directories. (b) Relative paths will stop working if you keep changing your current directory. Either go back to the previous directory or use absolute paths. (c) Don't use $(echo *), that's both broken and inefficient, just use */ (or "$PWD"/*/ for absolute paths). – jw013 Jan 13 '16 at 21:34
  • 1
    @Jakuje shell scripting is on-topic here, and this sounds like a permissions issue, which is definitely better off here. – Kevin Jan 13 '16 at 21:44
  • 2
    Without a subshell your script has only one working directory. – Cyrus Jan 13 '16 at 23:09
7

(Your question seems to have been answered as stated already; I want to address a different aspect of it.)

Even though you said "I know this is ridiculous", I'll just mention that running some command in every directory on a filesystem can be accomplished in a single line with:

find / -type d -exec sh -c 'cd "$1" && some_command' sh {} \;

This won't handle your permissions issue, but it's a lot simpler than writing a recursive shell function.

  • 1
    "but it's a lot simpler than writing a recursive shell function" sure is! +1 – Jeff Schaller Jan 15 '16 at 2:38
3

One of the main problems with the original script was that it changed into directories but never back out of them.

recurse ()
(
  recurse2 ()
  {
    [ $_recurse_stop -eq 1 ] && return
    cd "./$1" || return
    pwd ## do whatever you want in the pwd
    for entry in * .*;
    do
        [ "." = "$entry" -o ".." = "$entry" ] && continue;
        [ -d "$entry" -a ! -h "$entry" ] && recurse2 "$entry";
    done
    cd ..
  }

  _recurse_stop=0
  trap '_recurse_stop=1' 2
  recurse2 "$1"
)

Another change was to replace $(echo *) with the simple * glob.

I also made the simple fix of only trying to recurse down directories (the -d test).

After several insightful comments from @Wildcard and @mikeserv, this script now:

  • creates a top-level subshell to insulate all the cding around,
  • refuses to cd into symlink directories (! -h), and
  • sets up a trap to stop the recursion (via a signal variable) if it receives a ^C (interrupt) signal
-1

The simple example:

cdtree()
    if    OLDPWD=${1-.} cd -P - &&
          set . ./.[!.]*/ ./..?*/ ./*/ "" "${1%"${1#.}"}."
    then  while [ "${1:+1}" ]   && shift
          do    [ ! -d "$1" -o  -h "${1%/}" ]|| cdtree "$1"
          done; cd  -P "$2"
    else  printf %s\\n "$PWD/${1#./}"
    fi

It handles . directories named with a leading dot, it refuses to follow symlinks, and makes a reasonable attempt to restore the shell's current working directory to that in which it was launched.

It ignores all but its first argument, or else if called with no arguments it recurses the tree rooted in ..

It's capable:

{    find / -type d | wc -l
     cdtree /       | wc -l
}    2>/dev/null

23928
23928

And fairly quick. find walks my root tree in approximately .6 seconds. cdtree() in dash does it in 1.6 seconds. That's basically the same completion time as the older version. With the older version bash's runtime was excruciating - something like 20 times that of dash's. With this edit it's just under 5 seconds, and so is tolerable, but still a dog. I really don't understand bash's popularity.

The practical example:

cdtree()
    if    OLDPWD=${1-.} cd -P - &&
          "${cd_tree_callback-:}" "$PWD" &&
          set . ./.[!.]*/ ./..?*/ ./*/ "" "${1%"${1#.}"}."
    then  while [ "${1:+1}" ]   && shift
          do    [ ! -d "$1" -o  -h "${1%/}" ]|| cdtree "$1"
          done; cd  -P "$2"
    else  printf %s\\n "$PWD/${1#./}"
    fi

Because the only reason I can think one might want to do such a thing in the current shell process is to affect the current shell state in some way or else to run some current-shell-specific command per directory - that example makes it actually possible.

If $cd_tree_callback is set to the name of some command it will be run for each directory which cd_tree can directly change into. If the current working directory is changed as a result of doing so the above could turn out weird - but that's just a warning and you should do what you like. Otherwise, if it is unset the callback is a no-op, if set but empty, or not a valid command, or if the command called returns false then the tree recursion is cut off at that point, and only the directory name is printed to stdout before that level of the tree recursion returns to the previous.

Think of cd_tree_callback as something like a combination of find's -exec and -prune primitives.

And the very recursive example...

cdtree(){
        set '
#       \eval " \shift 0${3+1};'\
'               $1$1\\${1##* } \"\$PWD\" \"\$@\";'\
'       \eval   \"\${1#??}\";}; return"
        cdtree()
                if      OLDPWD=${1%/}   \cd -P -
                then    for  d  in      ./.[!.]*/ ./..?*/ ./*/
                        do      \[ ! -d "$d" -o -h "${d%/}" ] ||
                                \cdtree "${PWD%/}/${d#?/}" "$PWD"
                        done;   \cd -P  "${2:-.}"
                else    \printf %s\\n   "${PWD%/}/${1#?/}"
                fi
        while   \[  "${2+1}"  ] &&  \shift
        do      \[ "${1##/*}" ] &&  \set -- "$PWD$@"
                \[  / = "$1"  ] &&  \set -- "/$@"
                d=   \command eval "\cdtree \"\$1\" \"\$PWD\""
        done
        cdtree(){ \set '\' "$PWD" "$@"
        eval "${1#??}"
}

I forget what its called when a program can fully reproduce its own source at runtime... There's a name for that, but... it escapes me. Anyway, that is how that works. First it saves basically the entire body of the function, then, using that value, it defines a new cdtree() function which it can call from within the while loop for each arg. So you can hand it as many args as you like, and it will fully recurse the tree for each. When all of that is complete, it redefines itself again to its original state, so that it will do the same thing the next time you call it.

Apparently it's a kind of quine... though, this doesn't as much print its source to output as it does evaluate it, alter it, then reproduce and restore it.

  • 1
    Let me guess—your "very recursive" cdtree() is probably "self-evident", right? ;) – Wildcard Jan 21 '16 at 3:42
  • @Wildcard - no - but the others are... well - the 1st one is. the second one is explained. – mikeserv Jan 21 '16 at 4:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.