I just think that it is convenient for me to "cd" to the directory where I store some file, ie.
[admin#local /]$ cd /usr/bin/somefile.pl
which as far as I know that the official "cd" command will not work.
so I wrote something like this:
main () {
if [[ "${1}" =~ "(.+/)*(.*){1}" ]] && [ -f "${1}" ] ; then
`\cd ${1%/*}`
elif [ -f "${1}" ] ; then
exit 0
else ; `\cd ${1}`
fi
}
main ${1}
and I alias this cd.sh to the "cd" command:
alias cd='source /somepath/cd.sh'
and this doesn't work.
I've tried to use eval "\cd xxx" instead of just \cd xxx;
How can I fix my script?
It feels like a bad idea to override cd, so I'll suggest a slightly different command, fcd:
fcd() { cd -- "$(dirname -- "$1")"; }
$ fcd /usr/bin/somefile.pl
$ pwd
/usr/bin
Or using parameter expansion to save a call to dirname:
fcd { cd -- "${1%/*}"; }
cd() {
DN="$(dirname "$1")"
if [[ -d "$1" ]]; then
builtin cd "$1"
elif [[ -d "$DN" ]]; then
builtin cd "$DN"
else
echo "$* or $DN: No such directories"
return 1
fi
return 0
}
Related
For a while, I've had a need for a bash script to make a directory and cd into it. Most of the solutions online work but are very minimal so I wanted to make one that handles things like creating parent directories and permission checking. Here's my code:
#!/bin/bash
function mkcd() {
# Check for no arguments
if "$#" -eq 0; then
echo "Error: no arguments provided"
return 1
fi
# Checks if help flag is used
# Not with other flags to ensure the directory isn't assumed to be a flag
if [[ "$1" == "-h" || "$1" == "--help" ]]; then
echo "mkcd - Makes a directory and changes directory to it\n"
echo "Flags:"
echo " -h, --help Display help message"
echo " -p, --parents Makes parent directories as neeeded"
echo " -a, --absolute Receive an absolute directory instead of relative\n"
echo "Format: mkcd [arguments] directory"
return 0
fi
# Flag checker
while test "$#" -gt 1; do
case "$1" in
-p | --parents)
mkcd_parents=true
shift
;;
-a | --absolute)
shift
;;
esac
done
mkcd_path="$1"
if [[ ! -w "$PWD" ]]; then
echo "Error: Permission denied"
return 1
fi
if [[ -d "$mkcd_path" ]]; then
echo "Error: Directory already exists"
return 1
fi
if "$mkcd_parents"; then
mkdir -p "$mkcd_path"
cd "$mkcd_path"
else
mkdir "$mkcd_path"
cd "$mkcd_path"
fi
}
I also sourced it in my .zshrc file with source ~/bin/*
When I run the command, I get this output:
~ ❯ mkcd test_dir
mkcd:3: command not found: 1
mkcd:45: permission denied:
~/test_dir ❯
Does anyone understand why I'm getting this error?
if "$#" -eq 0; then
Since you have one argument to the script, that becomes after expansions
if 1 -eq 0; then
You probably meant to do
if [[ "$#" -eq 0 ]]; then
instead. (With either of [ .. ] or [[ .. ]].)
As an aside, I would change this
if "$mkcd_parents"; then
to
if [ "$mkcd_parents" = "true" ]; then
Otherwise if the -p option isn't given, $mkcd_parents is unset, "$mkcd_parents" expands to the empty string, and you get an error about that command not being found.
Shell script shortcuts or aliases are very useful for automating repetitive tasks in very few key strokes.
One example of example of "shortcut" command is take from zsh which does mkdir $dir && cd $dir.
I want to make something similar (medit) that does this: when called medit script.sh:
creates script
makes it executable (chmod +x)
populates it with shebang line
opens the file in the default editor
Can I do this using an alias so I would avoid writing a bash script that does that?
You better write a function like:
function medit(){
echo "#!/bin/bash" > "$1"
chmod +x "$1"
if [ -n "$VISUAL" ]; then
"$VISUAL" "$1"
elif [ -n "$EDITOR" ]; then
"$EDITOR" "$1"
else
vi "$1"
fi
}
Put it to .bashrc and call it with medit script.sh
It will first try to run the editor specified in $VISUAL and $EDITOR and falls back to vi if there is no standard editor specified.
I would write something like this:
# Usage: just like 'builtin printf'
fatal_error() {
builtin printf "$#" >&2
exit 1
}
# Usage: medit [file] [more arguments]
medit() {
# If we have arguments
if [ $# -gt 0 ]; then
# If $1 file does not exist, or is empty,
# put a shebang line into it. See `help test`.
if [ ! -s "$1" ]; then
printf "%s\n" "#!/bin/bash -" > "$1" || \
fatal_error "Failed to write to %s\n" "$1"
fi
# Make $1 executable
chmod +x "$1" || fatal_error "failed to chmod %s\n" "$1"
fi
# Run the default editor with the arguments passed to this function
"${EDITOR:-${VISUAL:-vi}}" "$#"
}
The medit command can be invoked just like the default editor, even without arguments.
On the off-chance that you don't know how to re-use the script above, put the code into some ~/scripts/medit, and source it from the appropriate initialization script such as ~/.bashrc: source ~/scripts/medit.
What you are asking for is an alias.
There is no way to do all you ask without using one or several functions.
But there is a way to make an alias define some functions and also call them:
alias medit='
SayError(){ local a=$1; shift; printf "%s\n" "$0: $#" >&2; exit "$a"; }
medit(){
[[ $# -lt 1 ]] &&
SayError 1 "We need at least the name of the file as an argument"
[[ ! -s $1 ]] && echo "#!/bin/bash" > "$1" ||
SayError 2 "File $1 already exists"
chmod u+x "$1" ||
SayError 3 "File $1 could not be made executable"
${VISUAL:-${EDITOR:-emacs}} "$1" ||
SayError 4 "File $1 could not be open in the editor"
}
\medit'
You need to execute the above definition of the alias medit or place it in ~/.bashrc, or simply source it in the running shell to make it exist.
Then, when the alias is called, it defines two functions: SayError and medit.
Yes, a function with the same name as the alias: medit.
After the function definition, the alias will call the function by using a trick:
\medit
As (strictly speaking) a \medit is not exactly the alias medit, bash keeps searching and finds the function medit, which by then has been defined and is executed.
Of course, you can just define the functions and use them without resorting to an alias to make the definition of the functions, that's your choice.
What is nice is to have the choice. :)
This is how you could define all in a sourced file:
alias medit='\medit'
SayError(){ local a=$1; shift; printf "%s\n" "$0: $#" >&2; exit "$a"; }
medit(){
[[ $# -lt 1 ]] &&
SayError 1 "We need at least the name of the file as an argument"
[[ ! -s $1 ]] && echo "#!/bin/bash" > "$1" ||
SayError 2 "File $1 already exists"
chmod u+x "$1" ||
SayError 3 "File $1 could not be made executable"
${VISUAL:-${EDITOR:-emacs}} "$1" ||
SayError 4 "File $1 could not be open in the editor"
}
I am trying to write a two argument function in bash. here is the code
cd() {
if [ $1 != .. ]; then builtin cd $1 ; ls
else
if [ -z "$2" ]; then
builtin cd ..
else
echo "$2"
int= $2
while [ $int > 0 ]; do
builtin cd ..
((int=int-1))
done
fi ;
fi ;
}
when i run it in terminal with this line cd .. 4 it will cd the parent and echo 4 but then it prints 4: command not found. Could anyone help me with this?
for more comfort I like to override mkdir like this:
mkdir() {
if [[ "$#" == *--parents* ]]; then
builtin mkdir "$#"
else
builtin mkdir "$#" --parents
fi
}
Unfortunately there is no builtin of mkdir. How can I do a workaround that does the job?
You can use the command built-in instead:
mkdir() {
if [[ "$#" == *--parents* ]]; then
command mkdir "$#"
else
command mkdir "$#" --parents
fi
}
mkdir_p=`which mkdir`
after that you can call it with:
$mkdir_p args...
or
alias mkdir=¨mkdir -p¨
I have a Bash function named "inDir" which abstracts the "go to a directory, do something, and come back to the starting directory" pattern. It is defined as:
inDir() {
if [ $# -gt 1 ]; then
local dir="$1"
local cwd=`pwd`
shift
if [ -d "$dir" ]; then
cd "$dir" && "$#"
cd "$cwd"
fi
fi
}
I am trying to create a function whose semantics don't matter much, but will essentially run:
inDir /tmp { [ -e testFile ] && touch testFile }
I hope the "implied" semantics are clear. I want to go into a directory, check if $somefile exists, and if it does, delete it. This is not working as intended. If I run:
cd
inDir /tmp [ -e testFile ] && touch testFile
it checks if testFile exists in /tmp, and then tries to touch it in ~. Can anybody think of a good way to invoke inDir so that it accepts "compound" commands?
indir() {
if [ -d "$1" ]; then
local dir="$1"
shift
(cd "$dir" && eval "$#")
fi
}
indir /tmp touch testFile
indir /tmp "[ -e testFile ] && rm testFile"
Nope. Just tell it to invoke a subshell.
inDir /tmp bash -c "[ -e testFile ] && touch testFile"