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?
-
4You call that a one-liner??Stéphane Gimenez– Stéphane Gimenez2011年12月17日 17:58:15 +00:00Commented Dec 17, 2011 at 17:58
-
1Do not cross-post. This is a duplicate of How do I get this Bash-script to work?Stéphane Gimenez– Stéphane Gimenez2011年12月17日 20:01:03 +00:00Commented Dec 17, 2011 at 20:01
-
Why do so many people try to do so much on one line? I don't get it!johnsyweb– johnsyweb2011年12月17日 20:07:21 +00:00Commented Dec 17, 2011 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– johnsyweb2011年12月17日 20:09:50 +00:00Commented Dec 17, 2011 at 20:09
-
1Please don't ask the same question multiple times.Paul Tomblin– Paul Tomblin2011年12月17日 23:36:42 +00:00Commented Dec 17, 2011 at 23:36
8 Answers 8
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
).
-
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– Robert Sundström2011年12月17日 14:22:30 +00:00Commented Dec 17, 2011 at 14:22
-
1Your 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– johnsyweb2011年12月18日 03:14:50 +00:00Commented Dec 18, 2011 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– Robert Sundström2011年12月18日 03:18:30 +00:00Commented Dec 18, 2011 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– johnsyweb2011年12月18日 04:24:01 +00:00Commented Dec 18, 2011 at 4:24 -
1N.B: If you just want to print the filename, you can omit
deepest=$( [...] )
and start fromfind
. It's not really clear what you're trying to achieve.johnsyweb– johnsyweb2011年12月18日 04:31:17 +00:00Commented Dec 18, 2011 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"
)
-
@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– manatwork2011年12月18日 12:34:34 +00:00Commented Dec 18, 2011 at 12:34 -
1@manatwork: Indeed I forgot to test again with
bash
. I usedzsh
and it was accepted. The best is maybe to use a subshell to deal with those little scope issues.Stéphane Gimenez– Stéphane Gimenez2011年12月18日 12:44:29 +00:00Commented Dec 18, 2011 at 12:44 -
A faster way to obtain the value of
C
isC=${LINE//[^\/]/}; C=${#C}
. Also, you don't need thedeclare
keyword if you're using a subshell to make variables local.Gilles 'SO- stop being evil'– Gilles 'SO- stop being evil'2011年12月18日 23:39:14 +00:00Commented Dec 18, 2011 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– Stéphane Gimenez2011年12月20日 11:18:34 +00:00Commented Dec 20, 2011 at 11:18
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?
With zsh:
f=(**/*(D.Oe:'REPLY=${REPLY//[^\/]}':[1])) && mv -- $f $f:h/new-name
We use zsh
recursive globbing (**/*
) and glob qualifiers (...)
, with D
to include dot-files, .
to only select regular files and Oe:'code'
to define a sort order (here reversed because of the upper case O
) based on the value of $REPLY
after execution of the code
.
In that code
, the current file is passed as $REPLY
. We remove everything but /
characters in that $REPLY
, and that's what is used for the sort order. So the file with the most /
sorts last, first once reversed and we select that first one with [1]
.
The bash
with (recent) GNU tools equivalent could be something like:
(
export LC_ALL=C
find . -type f -print0 |
sed -z 'h;s:[^/]::g;G;s:[^/]::' |
sort -rz |
sed -z 's:/*::;q' |
tr '0円' '\n'
)
-
I don't know
zsh
particularly, but I'm betting this is the only one of the many answers given so far that actually correctly handles any and all special characters in filenames, including newlines. (You should mention that in the answer if so; this deserves more attention.)Wildcard– Wildcard2016年11月13日 10:02:27 +00:00Commented Nov 13, 2016 at 10:02
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//'
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.
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
I am surprised no one mentioned using %d
.
You can list all nested subfolders, and sort them by depth, using:
find . -type d -printf '%d:%p\n' | sort -t: -k1 -g -r > /tmp/foo.txt
where
.
can be replaced with the root folder;-type d
finds nested subfolders (replace with-type f
for files instead);%d:%p
prints the depth, followed by the path;-t:
means the separator is:
;-k1
means we want to sort by the first key/column;-g
means that we want to sort numbers;-r
means that we want the deepest folders to show first.
The result of this command are streamed into file /tmp/foo.txt
(replace as desired, you can also use mktemp
if needed).
The deepest level can then be determined automatically with:
D=$(head -n 1 /tmp/foo.txt | awk -F ":" '{print 1ドル}')
If you want to filter only those folders or files at depth D
, you can use:
awk -F ":" '{ if (1ドル == D) print 2ドル }' /tmp/foo.txt
Finally, to select a line randomly amongst the results, pipe with | shuf -n 1
.
Putting this into a script random_deep_file <Folder> <Depth>
:
Folder=1ドル
# Sort files by depth into temporary file
Temp=$(mktemp)
find "$Folder" -type f -printf '%d:%p\n' | sort -t: -k1 -g -r >| "$Temp"
# Determine deepest level if not specified
[ $# -lt 2 ] && { D=$(head -n 1 "$Temp" | awk -F ":" '{print 1ドル}'); } || D=2ドル
# Select a random file at that depth
awk -F ":" "{ if (\1ドル == $D) print \2ドル }" "$Temp" | shuf -n 1
Note: If you are on OSX, you need to install (with Homebrew) and use
gfind
andgshuf
instead.