Calling a bash function isn't working as it should to change to recent directory?
Why your function is not working
There are few reasons:
- I guess
echo
is an artifact used for testing the syntax or so. It makes no sense if you want the function to do what you described. - Uppercase
HEAD
. In Linux it should behead
. I'm not sure about other OS-es where you can runbash
andhead
.HEAD
may or may not work in some of them buthead
should work everywhere. - Parsing
ls
is not recommended. There is an article about it. The main point in your case would be thatls
cannot reliably print names including special or unprintable characters. - There's no logic to test directories only, you may end up trying to
cd
to a file when there's no directory. - There's no logic to handle the situation when the current working directory is empty.
All these issues can be debugged except this parsing ls
thing. It's a design flaw. If you think ls
limitations won't bite you then you can go with a solution from this another answer.
To do some tests you may create a troublesome directory with mkdir "$(echo -ne "foo\nbar")"
; ls
-based solutions will probably fail if this is the directory cdrc
should cd
to. To remove the troublesome directory invoke rmdir "$(echo -ne "foo\nbar")"
.
I managed to create a safer function.
Solution
function cdrc { cd "$(find -maxdepth 1 -mindepth 1 -type d -exec stat --printf "%Y %n\0" {} + | sort -znr | head -zn 1 | cut -f 2- -d " ")" ;}
Explanation
To explain my function I shall write it more clearly. Note a \
at the very end of a line tells bash
the command continues in the next line; so my code below is treated as a one-liner, it can be pasted as a whole to interactive bash
.
function cdrc { \
cd "$( \
find -maxdepth 1 -mindepth 1 -type d -exec \
stat --printf "%Y %n\0" {} + |
sort -znr |
head -zn 1 |
cut -f 2- -d " " \
)" \
;}
The procedure is as follows:
- At first
find
is run. It doesn't descend to subdirectories (-maxdepth 1
), it doesn't find the current directory either (-mindepth 1
). It finds directories only (-type d
). Then thestat
command is run (thanks to-exec
):-
stat
prints time of last data modification (%Y
, mtime, seconds since Epoch), single space and name (%n
). Due to--printf
option it doesn't add the newline character but interprets\0
as a null character that should be added at the end of every line. -
{}
is a part offind -exec
syntax. Duringfind
execution it is replaced by directory name sostat
knows what its target is. -
+
is also a part offind -exec
syntax. It causesfind
to pass multiple names to singlestat
(andstat
can handle it). This way fewerstat
processes are created, it's faster.
-
At this moment we have zero or more lines. They look similar to this:
1493488341 directory name
1497365306 troublesome?directory name
but they are null-terminated, so even if there are names with troublesome characters, they will be handled properly. In the first column there are mtimes without leading spaces (I checked stat
behavior with numbers of various length to be sure), then the first space separates mtime from directory name.
- This output is processed furhter:
-
sort
sorts lines according to numerical value (-n
), uses reverse order (-r
) and works with null-terminated strings (-z
). This way the directory we need is now in the first line. - Then
head
leaves the first line only (-n 1
); it's also told to work with null-terminated strings (-z
). -
cut
cuts the line, treating space as delimiter (-d " "
) and leaving the second field and everything that follows (-f 2-
), i.e. everything after the first space. It works with null-terminated strings (-z
). The final output is the desired directory name.
-
Note the output will be empty if there's no directory in the current working directory.
-
$(…)
is replaced by the output of everything that is inside. At this moment we have eithercd "some directory name"
orcd ""
. The former command does what you want; the latter (when there's no directory) does nothing.
The function will fail if the directory it's supposed to cd
to is (re)moved/renamed after find
finds it. Also stat
may throw error(s) if any directory is (re)moved/renamed when the function works.