Can you explain these three things in this bash code for me?
d=$d/..
adds/..
to the current contents of thed
variable.d
starts off empty, then the first iteration makes it/..
, the second/../..
etc.sed 's/^\///'
drops the first/
, so/../..
becomes../..
(this can be done using a parameter expansion,d=${d#/}
).d=..
only makes sense in the context of its condition:if [ -z "$d" ]; then d=.. fi
This ensures that, if
d
is empty at this point, you go to the parent directory. (up
with no argument is equivalent tocd ..
.)
This approach is better than iterative cd ..
because it preserves cd -
— the ability to return to the previous directory (from the user’s perspective) in one step.
The function can be simplified:
up() {
local d=..
for ((i = 1; i < ${1:-1}; i++)); do d=$d/..; done
cd $d
}
This assumes we want to move up at least one level, and adds n - 1 levels, so we don’t need to remove the leading /
or check for an empty $d
.
Using Athena jot
(the athena-jot
package in Debian):
up() { cd $(jot -b .. -s / "${1:-1}"); }
(based on a variant suggested by glenn jackman).
But can you explain these three things from it for me?
d=$d/..
This concatenates the present contents of var
d
with/..
and assign it back tod
.
The end result is to maked
a repeated string like/../../../..
.sed 's/^///'
Remove the leading
/
from the string supplied,d
(echo $d) in the code you posted.
Probably better written assed 's|^/||'
to avoid the backslash.An alternative (faster and simpler) is to write
d=${d#/}
.d=..
Assign the string
..
to the vard
.
This only makes sense as a way to ensure thatd
has at least one..
in case the testif [ -z "$d" ]; then
signals that the vard
is empty. And that can only happen because the sed command is removing one character fromd
.
If there is no need to remove the character from d, there is no need forsed
orif
.
The code in your question will always move up at least one dir.
Better
local d
is enough to make sure that the variable is empty, nothing else is needed.
However, local works only in some shells like bash or dash. In specific,ksh
(asyash
) doesn't have alocal
command. A more portable solution is:[ "$KSH_VERSION$YASH_VERSION" ] && typeset c='' d='' i='' || local c='' d='' i=''
The
for((
syntax is not portable. Better use something like:while [ "$((i+=1))" -lt "$limit" ]; do
Robust.
If the values given to the function first argument could be negative or text, the function should be robust to process those values.
First, lets limit the values to only numbers (I'll usec
forcount
):c=${1%%[!0-9]*}
And then limit the count value only to positive values:
let "c = (c>0)?c:0"
This function will hapily accept 0 or any text (without errors):
up()
{
[ "$KSH_VERSION$YASH_VERSION" ] && typeset c='' d='' i='' || local c='' d='' i=''
c=${1%%[!0-9]*}
c=$(((c>0)?c:0))
while [ "$((i+=1))" -le "$c" ]; do d="$d../"; done
echo \
cd "${d%/}" # Removing the trailing / is not really needed for cd
# but it is nice to know it could be done cleanly.
}
up 2
up 0
up asdf
Remove the echo \
when you have tested the function.