A long time ago I created a script for moving up directories very quickly in the command line using the command up
. You can find usage notes here.
It's a very simple script with just 8 lines of source code, as follows:
if [ -z "1ドル" ]; then
cd ..
else
for i in `seq 1 1ドル`;
do
cd ..
done
fi
I've never personally had any problems with it since I made it and started using it myself — but are there any accidental or malicious inputs (particularly with the blind injection of 1ドル
) that might cause this to do something bad that I'm not aware of?
Given that the up
command isn't used for anything in any command line I'm aware of, I'd like to promote more widespread usage of this script file so that people can type up
instead of cd ..
all the time, saving three keystrokes (or more if they want to move up more directories) for a very common operation.
4 Answers 4
Your recommendation is to define alias up=". path/to/up"
so that when you type up 3
, it expands to . up 3
. However, since you want to take an optional argument and affect the state of the current shell, I think you would be better off defining a shell function instead.
As it turns out, the [ -z "1ドル" ]
special case is not necessary, since seq 1
just expands to 1
.
You end up executing n separate cd ..
commands for up n
. This leads to a usability bug: cd -
or cd $OLDPWD
, which normally take you back to the previous directory, don't work the way I expect.
Suggested solution:
up() {
cd $(for i in $(seq 1 1ドル) ; do echo -n ../ ; done)
}
-
\$\begingroup\$ So defining
up
in that way makes a literalcd -
useless? Or were you expectingup -
to work ascd -
as well? \$\endgroup\$Joe Z.– Joe Z.2015年10月26日 01:06:01 +00:00Commented Oct 26, 2015 at 1:06 -
5\$\begingroup\$ @JoeZ. the former -
cd -
will return you back one level after callingup n
withn > 1
. This causes an issue if you want to get back to where you were before you calledup n
. Concating the command into a singlecd
will preserve the functionality. \$\endgroup\$Boris the Spider– Boris the Spider2015年10月26日 16:18:34 +00:00Commented Oct 26, 2015 at 16:18 -
\$\begingroup\$ This is an extremely elegant solution and should be the accepted answer. Kudos. \$\endgroup\$Wildcard– Wildcard2015年11月29日 08:27:43 +00:00Commented Nov 29, 2015 at 8:27
This is a decent concept, but by looping on the cd
you loose some of the value of the $OLDPWD
function in the shell. For example, I often use the special construct cd -
in a shell, and that changes directory to the one you were in before.
Your code will make that impossible.
I would instead recommend that you instead build up a chain of ../
string values, like ../../../../
for 4 directories, and then just call cd
once, which will preserve the cd -
function, and the $OLDPWD
.
Additionally, this would be a good feature to include as a function in your code, rather than a script. Bash shell likes functions, and they make life easier.
Finally, if someone supplies a non-number as an argument, it will do odd things.
I played with your code, and came up with:
up () {
local count=1ドル
if [ -z "$count" ]; then
cd ..
return
fi
test "$count" -eq "$count" || return 1
local todir=""
for i in `seq 1 $count`;
do
todir="../$todir"
done
cd $todir
}
The features of the above code I like are:
- it is a function of the shell, so there's no additional script called.
- it checks the value is a number, by doing a numeric comparison on the value:
test "$count" -eq "$count"
(That will throw an error if the inputs are not integers) - it only does a single 'cd', so things like
cd -
still work.
I would add that to my ~/.bashrc file, or source it in to my current shell.
200_success's answer uses the evil eval
which in his context was unnecessary.
Update: Oh, what, bash? Let's use some brace expansion + evil things:
# This time we have to be evil. All code in this answer is CC0.
up() { [ "1ドル" -eq "1ドル" ] &>/dev/null || set -- 1; "cd \$(printf '../%.s' {1..1ドル})"; }
local
is not a that nice solution since POSIX doesn't contain local
. After some searching, I found out that it's possible to use printf to duplicate strings. After replacing the brace expansion with seq
, here is what I got:
up() { cd "$(printf '../%.s' $(seq 1 1ドル))"; }
P.S.: The use of seq
for simple looping is sometimes considered harmful, especially when used with for var in
. To make you feel less guilty:
# integer seq, @copyright CC0.
iseq() (
: ${iseq_fmt='%s\n'}
case "$#" in
(1) i=1 last=1ドル incr=1;;
(2) i=1ドル last=2ドル incr=1;;
(3) i=1ドル last=3ドル incr=2ドル;;
(*) return 2;;
esac
[ "$i" -eq "$i" ] && [ "$incr" -eq "$incr" ] && [ "$last" -eq "$last" ] || return 2
while [ "$i" -le "$last" ]; do
printf "$iseq_fmt" "$i"
: $((i = i + incr))
done
)
-
\$\begingroup\$ In my solution,
i
is scoped to a subshell. The prior value of$i
has no influence on the execution, nor does the execution alter the value of$i
in the main shell. \$\endgroup\$200_success– 200_success2015年10月26日 00:38:26 +00:00Commented Oct 26, 2015 at 0:38 -
\$\begingroup\$ @200_success Oh right, command substitution. \$\endgroup\$Mingye Wang– Mingye Wang2015年10月26日 00:46:34 +00:00Commented Oct 26, 2015 at 0:46
-
1\$\begingroup\$ @200_success But you still don't need that
eval
actually. \$\endgroup\$Mingye Wang– Mingye Wang2015年10月26日 00:47:20 +00:00Commented Oct 26, 2015 at 0:47
You probably want to forward any options passed to cd
, particularly -L
, -P
, -e
and -@
. Luckily, none of those take any arguments, so it's straightforward to catch them:
up() {
local options=()
for i in "$@"
do
case "$i" in
-*) options+=("$i") ;;
[1-9]*)
test "$i" -eq "$i" &&
cd "${options[@]}" $(perl -e "print'../'x$i;")
return ;;
esac
done
# If we got here, there were no non-option arguments
cd "${options[@]}" ..
}
I might name it ..
rather than up
.
-
\$\begingroup\$ Do you want
[1-9]*
or([1-9][0-9]*)?
? If you typedup 10
what would happen? \$\endgroup\$Joe Z.– Joe Z.2018年11月08日 23:35:42 +00:00Commented Nov 8, 2018 at 23:35 -
\$\begingroup\$ I'm confusing them with regexes. Thanks for telling me. \$\endgroup\$Joe Z.– Joe Z.2018年11月11日 03:22:51 +00:00Commented Nov 11, 2018 at 3:22
-
\$\begingroup\$ Er, yes, I meant regexps - sorry for the confusion. Anyway,
10
matches (I tested it). \$\endgroup\$Toby Speight– Toby Speight2018年11月12日 11:03:08 +00:00Commented Nov 12, 2018 at 11:03 -
\$\begingroup\$ What happens if you type
up 1P
? \$\endgroup\$Joe Z.– Joe Z.2018年11月12日 18:03:34 +00:00Commented Nov 12, 2018 at 18:03 -
\$\begingroup\$ Then the
test "$i" -eq "$i"
fails and nothing happens (it would be nicer if it gave the user an error message; that's an exercise for the reader) \$\endgroup\$Toby Speight– Toby Speight2018年11月12日 18:05:21 +00:00Commented Nov 12, 2018 at 18:05
..
. Also,...
,....
,.....
, ... \$\endgroup\$bash
. :P \$\endgroup\$alias f='cd ..;';alias ff='cd ..;cd ..;'alias fff='cd ..;cd ..;cd ..;';alias ffff='cd ..;cd ..;cd ..;cd ..;'; alias fffff='cd ..;cd ..;cd ..';cd ..;cd ..;
inbashrc
because there's only so many times you'd want to go up and the "f" key is easily accessible. Just a matter of typing "f" a few times and then hit enter instead of typeup 5
. The "f" stands for "fall" because from childhood, I consideredcd dirName
to be "climbing" directories andcd ..
to be falling from a climbed directory. \$\endgroup\$cd ../..
forff
? You iterated them independently by hand. \$\endgroup\$cd
to take me home. Since all the files I own and work on are within there. And usually getting back to working files are less than four or five tab-competes deep. Though more commonlycd ~/where/ever/
. [Just speaking generally about my personal cd usage.] \$\endgroup\$