Take the 2-minute tour ×
Unix & Linux Stack Exchange is a question and answer site for users of Linux, FreeBSD and other Un*x-like operating systems.. It's 100% free, no registration required.

How do I name one random file in the deepest level of a directory tree using basic Bash commands and script?

I was searching for a way to do it as an one-liner and without functions. Assuming also that I'm starting in the current directory and then work towards the depth.

find, grep, sed and awk are acceptable commands.

My current attempt looks like this and does not work:

find -type d | declare COUNT=-1; declare P=""; while read LINE ; do echo $LINE; declare C; C=$(echo $LINE | cut -c3- | sed "s/\//\n\//g" | grep '/' -c); echo $C; if [ $COUNT -gt $C ]; then let COUNT=$C; let P=$LINE; echo "Done"; fi; done

This would only find the directory.

How could this be solved in the most simple way?

share|improve this question

migrated from stackoverflow.com Dec 17 '11 at 3:33

This question came from our site for professional and enthusiast programmers.

2  
You call that a one-liner?? –  Stéphane Gimenez Dec 17 '11 at 17:58
1  
Do not cross-post. This is a duplicate of How do I get this Bash-script to work? –  Stéphane Gimenez Dec 17 '11 at 20:01
    
Why do so many people try to do so much on one line? I don't get it! –  Johnsyweb Dec 17 '11 at 20:07
    
Why should they be one-liners? As you've seen , when a one-liner goes wrong, it's very hard to debug! Open an editor and write a bash script! –  Johnsyweb Dec 17 '11 at 20:09
1  
Please don't ask the same question multiple times. –  Paul Tomblin Dec 17 '11 at 23:36

6 Answers 6

up vote 7 down vote accepted

That's an odd request!

I'd use find + awk to grab a file in the deepest directory:

bash-3.2$ deepest=$(find / -type f | awk -F'/' 'NF > depth {
>     depth = NF;
>     deepest = $0;
> }
>
> END {
>     print deepest;
> }')

Using ${deepest} in your mv command is left as an exercise but the following five lines may help you further:

bash-3.2$ echo "${deepest}"
/Developer/SDKs/MacOSX10.6.sdk/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/gems/1.8/gems/activesupport-2.3.5/lib/active_support/vendor/tzinfo-0.3.12/tzinfo/definitions/America/Argentina/Buenos_Aires.rb

bash-3.2$ echo "${deepest%.*}"
/Developer/SDKs/MacOSX10.6.sdk/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/gems/1.8/gems/activesupport-2.3.5/lib/active_support/vendor/tzinfo-0.3.12/tzinfo/definitions/America/Argentina/Buenos_Aires

bash-3.2$ echo "${deepest%/*}"
/Developer/SDKs/MacOSX10.6.sdk/System/Library/Frameworks/Ruby.framework/Versions/1.8/usr/lib/ruby/gems/1.8/gems/activesupport-2.3.5/lib/active_support/vendor/tzinfo-0.3.12/tzinfo/definitions/America/Argentina

bash-3.2$ echo "${deepest##*/}"
Buenos_Aires.rb

bash-3.2$ echo "${deepest##*.}"
rb

Following update to question:

find -type d [...] "This would only find the directory. [...] How could this be solved in the most simple way?".

By supplying -type f to find to find all files (f), not all directories (d).

share|improve this answer
    
I've updated my question and added the command-line script I wrote to attempt to do this. It's not working. Some feedback from others will be welcomed. –  Robert Sundström Dec 17 '11 at 14:22
    
Your one-liner is incomprehensible to me. I'm sorry I cannot debug that for you. Does my example not do what you asked for? –  Johnsyweb Dec 18 '11 at 3:14
    
Sorry. Yes, it does. I was just seeking for different ways of tackling the problem as I'm quite new to Bash and the paradigm. Your solution does it!! –  Robert Sundström Dec 18 '11 at 3:18
    
I see. There are many ways to solve this. I recommend my own solution since it involves only one |, keeping subprocesses to a minimum. –  Johnsyweb Dec 18 '11 at 4:24
    
N.B: If you just want to print the filename, you can omit deepest=$( [...] ) and start from find. It's not really clear what you're trying to achieve. –  Johnsyweb Dec 18 '11 at 4:31

You are missing some curly-bracketing, you inverted the comparison, and you need to print the result:

find -type d | {
  declare COUNT=-1
  declare P=""
  while read LINE
    do echo $LINE
    declare C=$(echo $LINE | cut -c3- | sed "s/\//\n\//g" | grep '/' -c)
    echo $C
    if [ $C -gt $COUNT ]; then let COUNT=$C; let P=$LINE; echo "Done"; fi
  done
  echo deepest: "$P"
}

Slightly improved version, with debugging stuff thrown away:

find -type d -links 2 | (
  declare COUNT=-1
  P=""
  while IFS= read -r LINE; do
    declare C=$(echo $LINE | tr -cd / | wc -c)
    if [ $C -gt $COUNT ]; then let COUNT=$C; P=$LINE; fi
  done
  echo deepest: "$P"
)
share|improve this answer
    
Thank you so much! –  Robert Sundström Dec 18 '11 at 3:11
    
@Stéphane, could you specify the shell you used? The question is tagged bash, but I suppose you used something else, as bash` gives this: “bash: local: can only be used in a function”. –  manatwork Dec 18 '11 at 12:34
1  
@manatwork: Indeed I forgot to test again with bash. I used zsh and it was accepted. The best is maybe to use a subshell to deal with those little scope issues. –  Stéphane Gimenez Dec 18 '11 at 12:44
    
A faster way to obtain the value of C is C=${LINE//[^\/]/}; C=${#C}. Also, you don't need the declare keyword if you're using a subshell to make variables local. –  Gilles Dec 18 '11 at 23:39
    
Clever trick. Oh, and I think the point of declare was to make those variables integer variables, so I did not changed it. –  Stéphane Gimenez Dec 20 '11 at 11:18

I would go with these, although they will fail on file names containing leading spaces. The first outputs just the file name, the second includes the path to that file too:

find -type f | sed 's:[^/]*/: :g' | LC_ALL=C sort | head -1 | sed 's/^ *//'

find -type f | sed 'h;s:[^/]*/: :g;G;s/\n/\t/' | LC_ALL=C sort | head -1 | sed 's/.*\t//'
share|improve this answer
function step () {
    d=$1
    d=$((d+1))
    res=$(find -mindepth $d -type d ! -empty)
    test -n "$res" && step $d || echo $((d-1))
}

find -mindepth $(step 0) -type d ! -empty | head -n 1 

Recursively step deeper and deeper into directories, as long as they contain a file (! -empty). From the last step, we have to subtract 1, and we can then use the result in a second find command.

share|improve this answer
find -type f | awk -F/ 'NF > maxdepth { maxdepth = NF; file = $0 }; END {print file}'

And it seems this is essentially the same as this answer on the other question you posted, what's wrong with that/this one?

share|improve this answer
    
Nothing actually. Thank you very much! –  Robert Sundström Dec 18 '11 at 3:14

The following script prints out the longest paths in the file system and/or below the current directory, but instead of using find, it takes advantage of the existing locate database of (potentially very long) paths without searching/traversing (potentially very deep) directory structures. Note it does count directories to determine depth, not just the length of the path (but it could easily be modified/simplified to do either).

$ cat find-longest-path.sh
#!/bin/bash
pattern=$1
locate -r "^${PWD}/${pattern}" \
   | while read f; do printf "$(tr -cd / <<< "$f" | wc -c):$f\n"; done \
   | sort -nr -t: -k1 \
   | head -5

This prints out the deepest 5 paths (head -5) of a file name pattern (regexp) starting from pwd. Output is prefixed by the depth (i.e., number of /'s in the path). To search for just a specific filename and/or pattern, anywhere in the file system, remove $PWD and just search for locate -r /some_file$ (...etc, etc).

For example,

$ ./find-longest-path.sh 'foo.*log$'
5:/path/to/deep/path/foo-12345.log
3:/path/to/shallow/foobar.log
share|improve this answer

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

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