Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
We don’t allow questions seeking recommendations for books, tools, software libraries, and more. You can edit the question so it can be answered with facts and citations.
Closed 15 days ago.
Improve this question
Is there any directory bookmarking utility for bash to allow move around faster on the command line?
Also, have a look at CDPATH
A colon-separated list of search paths available to the cd command, similar in function to the $PATH variable for binaries. The $CDPATH variable may be set in the local ~/.bashrc file.
ash$ cd bash-doc
bash: cd: bash-doc: No such file or directory
bash$ CDPATH=/usr/share/doc
bash$ cd bash-doc
/usr/share/doc/bash-doc
bash$ echo $PWD
/usr/share/doc/bash-doc
and
cd -
It's the command-line equivalent of the back button (takes you to the previous directory you were in).
Thanks for sharing your solution, and I'd like to share mine as well, which I find more useful than anything else I've came across before.
The engine is a great, universal tool: command-line fuzzy finder by Junegunn.
It primarily allows you to “fuzzy-find” files in a number of ways, but it also allows to feed arbitrary text data to it and filter this data. So, the shortcuts idea is simple: all we need is to maintain a file with paths (which are shortcuts), and fuzzy-filter this file. Here's how it looks: we type cdg command (from "cd global", if you like), get a list of our bookmarks, pick the needed one in just a few keystrokes, and press Enter. Working directory is changed to the picked item:
It is extremely fast and convenient: usually I just type 3-4 letters of the needed item, and all others are already filtered out. Additionally, of course we can move through list with arrow keys or with vim-like keybindings Ctrl+j/Ctrl+k.
Article with details: Fuzzy shortcuts for your shell.
It is possible to use it for GUI applications as well (via xterm): I use that for my GUI file manager Double Commander. I have plans to write an article about this use case, too.
Bashmarks is an amazingly simple and intuitive utility.
In short, after installation, the usage is:
s <bookmark_name> - Saves the current directory as "bookmark_name"
g <bookmark_name> - Goes (cd) to the directory associated with "bookmark_name"
p <bookmark_name> - Prints the directory associated with "bookmark_name"
d <bookmark_name> - Deletes the bookmark
l - Lists all available bookmarks
In bash script/command,
you can use pushd and popd
pushd
Save and then change the current directory. With no arguments, pushd exchanges the top two directories.
Usage
cd /abc
pushd /xxx <-- save /abc to environment variables and cd to /xxx
pushd /zzz
pushd +1 <-- cd /xxx
popd is to remove the variable (reverse manner)
bookmarks.sh provides a bookmark management system for the Bash version 4.0+. It can also use a Midnight Commander hotlist.
Yes there is DirB: Directory Bookmarks for Bash well explained in this Linux Journal article
An example from the article:
% cd ~/Desktop
% s d # save(bookmark) ~/Desktop as d
% cd /tmp # go somewhere
% pwd
/tmp
% g d # go to the desktop
% pwd
/home/Desktop
Inspired by the question and answers here, I added the lines below to my ~/.bashrc file.
With this you have a favdir command (function) to manage your favorites and a autocompletion function to select an item from these favorites.
# ---------
# Favorites
# ---------
__favdirs_storage=~/.favdirs
__favdirs=( "$HOME" )
containsElement () {
local e
for e in "${#:2}"; do [[ "$e" == "$1" ]] && return 0; done
return 1
}
function favdirs() {
local cur
local IFS
local GLOBIGNORE
case $1 in
list)
echo "favorite folders ..."
printf -- ' - %s\n' "${__favdirs[#]}"
;;
load)
if [[ ! -e $__favdirs_storage ]] ; then
favdirs save
fi
# mapfile requires bash 4 / my OS-X bash vers. is 3.2.53 (from 2007 !!?!).
# mapfile -t __favdirs < $__favdirs_storage
IFS=$'\r\n' GLOBIGNORE='*' __favdirs=($(< $__favdirs_storage))
;;
save)
printf -- '%s\n' "${__favdirs[#]}" > $__favdirs_storage
;;
add)
cur=${2-$(pwd)}
favdirs load
if containsElement "$cur" "${__favdirs[#]}" ; then
echo "'$cur' allready exists in favorites"
else
__favdirs+=( "$cur" )
favdirs save
echo "'$cur' added to favorites"
fi
;;
del)
cur=${2-$(pwd)}
favdirs load
local i=0
for fav in ${__favdirs[#]}; do
if [ "$fav" = "$cur" ]; then
echo "delete '$cur' from favorites"
unset __favdirs[$i]
favdirs save
break
fi
let i++
done
;;
*)
echo "Manage favorite folders."
echo ""
echo "usage: favdirs [ list | load | save | add | del ]"
echo ""
echo " list : list favorite folders"
echo " load : load favorite folders from $__favdirs_storage"
echo " save : save favorite directories to $__favdirs_storage"
echo " add : add directory to favorites [default pwd $(pwd)]."
echo " del : delete directory from favorites [default pwd $(pwd)]."
esac
} && favdirs load
function __favdirs_compl_command() {
COMPREPLY=( $( compgen -W "list load save add del" -- ${COMP_WORDS[COMP_CWORD]}))
} && complete -o default -F __favdirs_compl_command favdirs
function __favdirs_compl() {
local IFS=$'\n'
COMPREPLY=( $( compgen -W "${__favdirs[*]}" -- ${COMP_WORDS[COMP_CWORD]}))
}
alias _cd='cd'
complete -F __favdirs_compl _cd
Within the last two lines, an alias to change the current directory (with autocompletion) is created. With this alias (_cd) you are able to change to one of your favorite directories. May you wan't to change this alias to something which fits your needs.
With the function favdirs you can manage your favorites (see usage).
$ favdirs
Manage favorite folders.
usage: favdirs [ list | load | save | add | del ]
list : list favorite folders
load : load favorite folders from ~/.favdirs
save : save favorite directories to ~/.favdirs
add : add directory to favorites [default pwd /tmp ].
del : delete directory from favorites [default pwd /tmp ].
#getmizanur
I used your cdb script. I enhanced it slightly by adding bookmarks tab completion. Here's my version of your cdb script.
_cdb()
{
local _script_commands=$(ls -1 ~/.cd_bookmarks/)
local cur=${COMP_WORDS[COMP_CWORD]}
COMPREPLY=( $(compgen -W "${_script_commands}" -- $cur) )
}
complete -F _cdb cdb
function cdb() {
local USAGE="Usage: cdb [-h|-c|-d|-g|-l|-s] [bookmark]\n
\t[-h or no args] - prints usage help\n
\t[-c bookmark] - create bookmark\n
\t[-d bookmark] - delete bookmark\n
\t[-g bookmark] - goto bookmark\n
\t[-l] - list bookmarks\n
\t[-s bookmark] - show bookmark location\n
\t[bookmark] - same as [-g bookmark]\n
Press tab for bookmark completion.\n"
if [ ! -e ~/.cd_bookmarks ] ; then
mkdir ~/.cd_bookmarks
fi
case $1 in
# create bookmark
-c) shift
if [ ! -f ~/.cd_bookmarks/$1 ] ; then
echo "cd `pwd`" > ~/.cd_bookmarks/"$1"
complete -F _cdb cdb
else
echo "Try again! Looks like there is already a bookmark '$1'"
fi
;;
# goto bookmark
-g) shift
if [ -f ~/.cd_bookmarks/$1 ] ; then
source ~/.cd_bookmarks/"$1"
else
echo "Mmm...looks like your bookmark has spontaneously combusted. What I mean to say is that your bookmark does not exist." ;
fi
;;
# show bookmark
-s) shift
if [ -f ~/.cd_bookmarks/$1 ] ; then
cat ~/.cd_bookmarks/"$1"
else
echo "Mmm...looks like your bookmark has spontaneously combusted. What I mean to say is that your bookmark does not exist." ;
fi
;;
# delete bookmark
-d) shift
if [ -f ~/.cd_bookmarks/$1 ] ; then
rm ~/.cd_bookmarks/"$1" ;
else
echo "Oops, forgot to specify the bookmark" ;
fi
;;
# list bookmarks
-l) shift
ls -1 ~/.cd_bookmarks/ ;
;;
-h) echo -e $USAGE ;
;;
# goto bookmark by default
*)
if [ -z "$1" ] ; then
echo -e $USAGE
elif [ -f ~/.cd_bookmarks/$1 ] ; then
source ~/.cd_bookmarks/"$1"
else
echo "Mmm...looks like your bookmark has spontaneously combusted. What I mean to say is that your bookmark does not exist." ;
fi
;;
esac
}
Yes, one that I have written, that is called anc.
https://github.com/tobimensch/anc
Anc stands for anchor, but anc's anchors are really just bookmarks.
It's designed for ease of use and there're multiple ways of navigating, either by giving a text pattern, using numbers, interactively, by going back, or using [TAB] completion.
I'm actively working on it and open to input on how to make it better.
Allow me to paste the examples from anc's github page here:
# make the current directory the default anchor:
$ anc s
# go to /etc, then /, then /usr/local and then back to the default anchor:
$ cd /etc; cd ..; cd usr/local; anc
# go back to /usr/local :
$ anc b
# add another anchor:
$ anc a $HOME/test
# view the list of anchors (the default one has the asterisk):
$ anc l
(0) /path/to/first/anchor *
(1) /home/usr/test
# jump to the anchor we just added:
# by using its anchor number
$ anc 1
# or by jumping to the last anchor in the list
$ anc -1
# add multiple anchors:
$ anc a $HOME/projects/first $HOME/projects/second $HOME/documents/first
# use text matching to jump to $HOME/projects/first
$ anc pro fir
# use text matching to jump to $HOME/documents/first
$ anc doc fir
# add anchor and jump to it using an absolute path
$ anc /etc
# is the same as
$ anc a /etc; anc -1
# add anchor and jump to it using a relative path
$ anc ./X11 #note that "./" is required for relative paths
# is the same as
$ anc a X11; anc -1
# using wildcards you can add many anchors at once
$ anc a $HOME/projects/*
# use shell completion to see a list of matching anchors
# and select the one you want to jump to directly
$ anc pro[TAB]
In addition to #Dmitri Frank's answer - I have implemented the cdb command (aka cd bookmark) via simple alias (add this line to your ~/.bash_profile):
alias b='cat ~/.cd-bookmarks | grep -v "^\s*#" | grep -v "^\s*$" | fzf'
alias cdb='cd "$(b)"'
Create file ~/.cd-bookmarks and enter your paths, one per line. It supports empty lines and comments via #:
$ cat ~/.cd-bookmarks
### Devel stuff ###
/Users/pujezdsky/devel/projects/stackoverflow/
### Photo stuff ###
/Users/pujezdsky/Photos/
/Users/pujezdsky/Photos/last-import/
/Users/pujezdsky/Photos/dir with spaces/
Unfortunatelly it does not support ~ expansion so enter full paths.
Then you are able to do
$ cdb
And because of b alias even some more advanced stuff
$ mc `b`
$ cp my-file.txt `b`
$ touch `b`/test.sh
Unfortunately though, if you have spaces in your bookmark paths, you have to quote the `b` call. That makes it less handsome:
$mc "`b`"
Note 1:
Before you do this, check if you already have cdb, b commands / aliases to avoid their overwrite and malfunction. The easiest way is to use these commands that should return something like -bash: type: cdb: not found
$ type cdb
$ type b
Note 2:
The b alias can be simplified to
alias b='egrep -v "(^\s*#|^\s*$)" ~/.cd-bookmarks | fzf'
Note 3:
You can also make alias for adding current folder into your bookmarks. It is as simple as
alias ba='pwd >> ~/.cd-bookmarks'
For short term shortcuts, I have a the following in my respective init script (Sorry. I can't find the source right now and didn't bother then):
function b() {
alias $1="cd `pwd -P`"
}
Usage:
In any directory that you want to bookmark type
b THEDIR # <THEDIR> being the name of your 'bookmark'
It will create an alias to cd (back) to here.
To return to a 'bookmarked' dir type
THEDIR
It will run the stored alias and cd back there.
Caution: Use only if you understand that this might override existing shell aliases and what that means.
As practice shows, not so many bookmarks are needed every day.
So we can store them inside the script:
function go {
# go home dir if no params
if [ $# -eq 0 ]; then cd ~; return; fi;
# declare an assoc array with CAPITAL -A switch
declare -A o
# declare aliases and targets
o[apd]=$APPDATA
o[cli]='/mnt/c/CLI/'
o[closk]='/mnt/d/JOB/CLosk.Work/Dev._default/'
o[ds]='/mnt/d/JOB/DS/'
o[gh]='/mnt/d/Alpha/GitHub/'
o[pf]='/mnt/c/Program Files/'
o[wsa]='/mnt/d/JOB/WSA/Dev/'
# here we go
if [ ${o[$1]+_} ]; then cd "${o[$1]}"; fi
}
Using associative array let the list of links to be corrected with ease.
As you can see this script successfully used under Windows also.
I'm using this script under CentOS and Ubuntu too. With other links of course.
Also, I'm using this:
alias ~='go'
So:
~ # go to home dir
~ apd # go to system Application Data folder
And so on.
I wrote this function some time ago in ~/.bashrc:
cdfav() {
local favfile
local tmpfavfile
local match
favfile=~/.cdfav
if [ "$1" = "" ]; then
awk '{print i++ " " $0}' $favfile
return $?
fi
if [ "$1" = "-a" ]; then
readlink -f $2 >> $favfile
return $?
fi
if [ "$1" = "-d" ]; then
tmpfavfile=/tmp/.cdfav-tmp-${$}
awk '{print i++ " " $0}' $favfile | sed "/$2/d" | cut -f2- -d' ' > $tmpfavfile
mv $tmpfavfile $favfile
return 0
fi
if [ ! -e $favfile ]; then
return 1
fi
match=$(awk '{print i++ " " $0}' $favfile | grep "$1" | head -1 | cut -f2- -d' ')
if [ "$match" = "" ]; then
return 1
fi
cd $match
return $?
}
Usage examples:
~$ cdfav # lists favorite dirs
0 /home/user/dev/linux/
1 /home/user/dev/linux/references/v7unix
~$ cdfav ^1 # match 1 dir
~/dev/linux$ cdfav refe # no need for typing the whole dir name/path. The directory will be the first matched
~/dev/linux/references/v7unix$
I can add a (current) directory to my list with "-a ."
~/dev/linux/references/v7unix$ cd v7/
~/dev/linux/references/v7unix/v7$ cdfav -a .
~/dev/linux/references/v7unix/v7$ cdfav
0 /home/user/dev/linux/
1 /home/user/dev/linux/references/v7unix
2 /home/user/dev/linux/references/v7unix/v7
or remove a directories containing substring/matching regexp (with gnu grep on my machine) like this:
~/dev/linux/references/v7unix/v7$ cdfav -d v7
~/dev/linux/references/v7unix/v7$ cdfav
0 /home/user/dev/linux/
Have not tested with other shells like oksh yet, but I think it will work there also without much tweaking.
Related
This is related to Function to search of multiple patterns using grep
I want to search multiple files with multiple patterns using command such as follows:
myscript *.txt pattern1 pattern2 pattern3
I tried implementing the codes in the previous question but they do not work with wildcards. For example, following does not work:
#!/bin/bash
ARGS=$#
if [ $ARGS -lt 2 ]
then
echo "You entered only $ARGS arguments- at least 2 are needed."
exit
fi
search() {
if [ $# -gt 0 ]
then
local pat=$1
shift
grep -i "$pat" | search "$#"
else
cat
fi
}
for VAR in $1
do
file=$VAR
shift
cat "$file" | search "$#"
done
How can I create a script which can search for multiple files (taking it from first argument) to search multiple patterns (from rest of arguments)?
Did you try to use find and sed?
find . -name *.txt -exec sed -n -e '/pattern1/p' -e '/pattern2/' '{}' ';'
The -n option will make sure sed does not print all the file, and the p command prints the matching lines. Finaly, find will get all the files you need.
EDIT:
If you want to put that in a script to generate the sed command, you can use this trick.
EDIT 2:
As #shellter said, it is usually better to use options, and as your script is written, *.txt will be expanded by bash. To avoid that, you'll need to quote the first argument.
As usual, there is several solutions to your problem:
Solution 1 (Using bash built-in):
#! /usr/bin/env bash
set -o nounset # Throw error if variable not set
set -o errexit # Exit if error is thrown
work_dir=$PWD # directory to search from
# Reading the command line
files_pattern=${1:-}; # Save first argument as files pattern.
shift 1; # Move $1 to next argument (and propagate such as $n gets $n+1)
echo "==> Files to search follow pattern: ${files_pattern}"
_len=$#; #save the number of arguments.
for (( i=0; i<$_len; i=$i+1 )); # Go through the search patterns.
do
search_patterns[$i]=$1; # store the next search pattern
shift 1; # move $1 to next patern.
echo "==> New search pattern #$i: ${search_patterns[$i]}"
done
while read -r file; # Go through all the matching files
do
echo "==> In file: ${file}"
while read -r line; # Go though all the lines in the file
do
for regex in "${search_patterns[#]}"; # iterate trough patterns
do
[[ "${line}" =~ $regex ]] && echo "${line}";
done
done < ${file}
done < <(find $work_dir -iname $files_pattern -print) # find all the files matching file_pattern
Solution 2 (using grep):
#! /usr/bin/env bash
set -o nounset # Throw error if variable not set
set -o errexit # Exit if error is thrown
work_dir=$PWD # directory to search from
# Reading the command line
files_pattern=${1:-}; # Save first argument as files pattern.
shift 1; # Move $1 to next argument (and propagate such as $n gets $n+1)
echo "==> Files to search follow pattern: ${files_pattern}"
while [ $# -gt 0 ]; # Go through the search patterns.
do
search_patterns+="$1"; # store the next search pattern
shift 1; # move $1 to next patern.
[ $# -gt 0 ] && search_patterns+="|" #Add or option
done
echo "==> Search patterns: ${search_patterns}"
cd ${work_dir} && egrep -iR '('"${search_patterns}"')' && cd -;
Solution 3 (Using sed):
#! /usr/bin/env bash
set -o nounset # Throw error if variable not set
set -o errexit # Exit if error is thrown
work_dir=$PWD # directory to search from
# Reading the command line
files_pattern=${1:-}; # Save first argument as files pattern.
shift 1; # Move $1 to next argument (and propagate such as $n gets $n+1)
echo "==> Files to search follow pattern: ${files_pattern}"
while [ $# -gt 0 ]; # Go through the search patterns.
do
search_patterns+="/$1/p;"; # store the next search pattern
shift 1; # move $1 to next patern.
[ $# -gt 0 ] && search_patterns+=" " #Add or option
done
echo "==> Search patterns: ${search_patterns}"
# Will print file names, and then matching lines
find "$work_dir" -iname "$files_pattern" -print -exec sed -n "${search_patterns}" '{}' ';'
I am sure there is plenty other ways to tweak or solve this problem, but this should get you started.
Good Luck!
I have been trying to make a shell script that will split text files one after the other through an entire folder and deposit every split chunk into another designated folder.
Here is what I have so far, I know its probably clunky(have never tried writing a .sh before):
#!/bin/bash
#File Split Automation
echo "Usage: split [Folder w/ Input] [Folder For Outputs] [Options] [PREFIX]
Options: -b [sizeMB]: Split by size
-l [No. of Lines]: Split by Lines
If No Output Folder is Defined Default is Set To: /Desktop/splitter-parts
If No Options Are Selected Default is Size=100MB"
inputdirc=$1
outputdirc=$2
spltion=$3
meastick=$4
prefixture=$5
if [ -d $1 ]
then
echo "You Picked The Folder $1 To Split Files From"
ls $1
else
exit
fi
if [ -d $2 ]
then
echo "Please Confirm Folder Path For Output $outputdirc"
else
cd /root/Desktop/
mkdir -p splitter-parts
fi
read -t 10 -p "Press Enter Or Wait 5 Sec. To Continue"
cd $2
for swordfile in $( ls $1);
do
command -p split $3 $4 -a 3 -d $swordfile $5
done
Anything you see going wrong? Because I am not getting the output I desired, though it functioned fine when I just had a file and a folder in the split-command string.
EDIT::::
Sorry, I apologize. Just getting a bit ahead of myself.
This is what I am seeing when I run it:
root#kali:~/Desktop/Wordlists# ./splitter.sh '/root/Desktop/Wordlists' ' /root/Desktop/Untitled Folder' s 100MB
Usage: split [Folder w/ Input] [Folder For Outputs] [Options] [PREFIX]
Options: -b [sizeMB]: Split by size
-l [No. of Lines]: Split by Lines
If No Output Folder is Defined Default is Set To: /Desktop/splitter-parts
If No Options Are Selected Default is Size=100MB
You Picked The Folder /root/Desktop/Wordlists To Split Files From
10dig10milup2.txt mixed.txt
10dig10miluplow2.txt movie-characters.txt
10dig10miluplow3.txt name1s.txt
((------------------CUT------------)
lower.lst xae2.txt
lower.txt xaf2.txt
mangled.lst xag2.txt
mangled.txt xah6.txt
misc-dictionary.txt
./splitter.sh: line 24: [: /root/Desktop/Untitled: binary operator expected
Press Enter Or Wait 5 Sec. To Continue
./splitter.sh: line 37: cd: /root/Desktop/Untitled: No such file or directory
split: extra operand `10dig10milup2.txt'
Try `split --help' for more information.
split: extra operand `10dig10miluplow2.txt'
Try `split --help' for more information.
split: extra operand `10dig10miluplow3.txt'
Try `split --help' for more information.
split: extra operand `10dig10miluplow4.txt'
Try `split --help' for more information.
...................MORE OF THE SAME.......
As far as what I am supposed to see, I haven't gotten that far yet, clearly I am missing some steps.
A quick rewrite with some notes to follow:
#!/bin/bash
#File Split Automation
usage="Usage: split [Options] [Folder w/ Input] [Folder For Outputs] [PREFIX]
Options: -b [sizeMB]: Split by size
-l [No. of Lines]: Split by Lines
If No Output Folder is Defined Default is Set To: /Desktop/splitter-parts
If No Options Are Selected Default is Size=100MB"
split_opt="-b 100MB"
while getopts hb:l: opt; do
case $opt in
h) echo "$usage"; exit ;;
b) split_opt="-b $OPTARG" ;;
l) split_opt="-l $OPTARG" ;;
esac
done
shift $((OPTIND - 1))
if [[ $# -eq 0 ]]; then
echo "$usage"
exit 1
fi
inputdirc=$1
if [[ -d $inputdirc ]]; then
ls $1
else
echo "no such directory: $inputdirc" >&2
exit 1
fi
if [[ -n $2 ]]; then
outputdirc=$2
else
outputdirc=/root/Desktop/splitter-parts
fi
prefixture=$3
mkdir -p "$outputdirc"
cd "$outputdirc"
for swordfile in "$inputdirc"/*; do
command -p split $split_opt -a 3 -d "$swordfile" $prefixture
done
Notes:
you generally want to quote all your variables. This is the cause of your error, because there was a file with whitespace and square brackets in the name.
I did not quote a couple in the split command because I specifically want the shell to perform word splitting on the values
since options are, well, optional, use getopts to collect them.
you store the positional parameters in variables, but you continue to use the positional parameters. Pick one or the other.
I am looking for a script that recursively lists all files using export and read link and by not using ls options. I have tried the following code, but it does not fulfill the purpose. Please can you help.
My Code-
#!/bin/bash
for i in `find . -print|cut -d"/" -f2`
do
if [ -d $i ]
then
echo "Hello"
else
cd $i
echo *
fi
done
Here's a simple recursive function which does a directory listing:
list_dir() {
local i # do not use a global variable in our for loop
# ...note that 'local' is not POSIX sh, but even ash
# and dash support it.
[[ -n $1 ]] || set -- . # if no parameter is passed, default to '.'
for i in "$1"/*; do # look at directory contents
if [ -d "$i" ]; then # if our content is a directory...
list_dir "$i" # ...then recurse.
else # if our content is not a directory...
echo "Found a file: $i" # ...then list it.
fi
done
}
Alternately, if by "recurse", you just mean that you want the listing to be recursive, and can accept your code not doing any recursion itself:
#!/bin/bash
# ^-- we use non-POSIX features here, so shebang must not be #!/bin/sh
while IFS='' read -r -d '' filename; do
if [ -f "$filename" ]; then
echo "Found a file: $filename"
fi
done < <(find . -print0)
Doing this safely calls for using -print0, so that names are separated by NULs (the only character which cannot exist in a filename; newlines within names are valid.
I want to create a bash alias to do the following:
Assume I am at the following path:
/dir1/dir2/dir3/...../dirN
I want to go up to dir3 directly without using cd ... I will just write cdd dir3 and it should go directly to /dir1/dir2/dir3. cdd is my alias name.
I wrote the following alias, but it doesn't work:
alias cdd='export newDir=$1; export myPath=`pwd | sed "s/\/$newDir\/.*/\/$newDir/"`; cd $myPath'
Simply it should get the current full path, then remove anything after the new destination directory, then cd to this new path
The problem with my command is that $1 doesn't get my input to the command cdd
This is a slightly simpler function that I think achieves what you're trying to do:
cdd() { cd ${PWD/$1*}$1; }
Explanation:
${PWD/$1*}$1 takes the current working directory and strips off everything after the string passed to it (the target directory), then adds that string back. This is then used as an argument for cd. I didn't bother adding any error handling as cdwill take care of that itself.
Example:
[atticus:pgl]:~/tmp/a/b/c/d/e/f $ cdd b
[atticus:pgl]:~/tmp/a/b $
It's a little ugly, but it works.
Here's a function - which you could place in your shell profile - which does what you want; note that in addition to directory names it also supports levels (e.g., cdd 2 to go up 2 levels in the hierarchy); just using cdd will move up to the parent directory.
Also note that matching is case-INsensitive.
The code is taken from "How can I replace a command line argument with tab completion?", where you'll also find a way to add complementary tab-completion for ancestral directory names.
cdd ()
{
local dir='../';
[[ "$1" == '-h' || "$1" == '--help' ]] && {
echo -e "usage:
$FUNCNAME [n]
$FUNCNAME dirname
Moves up N levels in the path to the current working directory, 1 by default.
If DIRNAME is given, it must be the full name of an ancestral directory (case does not matter).
If there are multiple matches, the one *lowest* in the hierarchy is changed to." && return 0
};
if [[ -n "$1" ]]; then
if [[ $1 =~ ^[0-9]+$ ]]; then
local strpath=$( printf "%${1}s" );
dir=${strpath// /$dir};
else
if [[ $1 =~ ^/ ]]; then
dir=$1;
else
local wdLower=$(echo -n "$PWD" | tr '[:upper:]' '[:lower:]');
local tokenLower=$(echo -n "$1" | tr '[:upper:]' '[:lower:]');
local newParentDirLower=${wdLower%/$tokenLower/*};
[[ "$newParentDirLower" == "$wdLower" ]] && {
echo "$FUNCNAME: No ancestral directory named '$1' found." 1>&2;
return 1
};
local targetDirPathLength=$(( ${#newParentDirLower} + 1 + ${#tokenLower} ));
dir=${PWD:0:$targetDirPathLength};
fi;
fi;
fi;
pushd "$dir" > /dev/null
}
I agree with mklement0, this should be a function. But a simpler one.
Add this to your .bashrc:
cdd () {
newDir="${PWD%%$1*}$1"
if [ ! -d "$newDir" ]; then
echo "cdd: $1: No such file or directory" >&2
return 1
fi
cd "${newDir}"
}
Note that if $1 (your search string) appears more than once in the path, this function will prefer the first one. Note also that if $1 is a substring of a path, it will not be found. For example:
[ghoti#pc ~]$ mkdir -p /tmp/foo/bar/baz/foo/one
[ghoti#pc ~]$ cd /tmp/foo/bar/baz/foo/one
[ghoti#pc /tmp/foo/bar/baz/foo/one]$ cdd foo
[ghoti#pc /tmp/foo]$ cd -
/tmp/foo/bar/baz/foo/one
[ghoti#pc /tmp/foo/bar/baz/foo/one]$ cdd fo
cdd: fo: No such file or directory
If you'd like to include the functionality of going up 2 levels by running cdd 2, this might work:
cdd () {
newDir="${PWD%%$1*}$1"
if [ "$1" -gt 0 -a "$1" = "${1%%.*}" -a ! -d "$1" ]; then
newDir=""
for _ in $(seq 1 $1); do
newDir="../${newDir}"
done
cd $newDir
return 0
elif [ ! -d "$newDir" ]; then
echo "cdd: $1: No such file or directory" >&2
return 1
fi
cd "${newDir}"
}
The long if statement verifies that you've supplied an integer that is not itself a directory. We build a new $newDir so that you can cd - to get back to your original location if you want.
I constantly need to attach new paths to the PATH environment variable in .bashrc, like below:
export PATH=/usr/local/bin:$PATH
Then to make it take effect, I always do 'source ~/.bashrc' or '. ~/.bashrc', while I found one shortcoming of doing so which make me uncomfortable.
If I keep doing so, the PATH will getting longer and longer with many duplicated entries, for example in the previous command, if I source it twice, the value of PATH will be
PATH=/usr/local/bin:/usr/local/bin:/usr/local/bin:$PATH(<-the original path).
Is there a more decent way to attach new path to PATH in bashrc without making it ugly?
Another way is to check if OPATH isn't set. If it is, set it to PATH. This will be your original PATH.
if [ "$OPATH" == "" ];
then
OPATH=$PATH
fi
PATH=~/bin:$OPATH
(Code is untested...)
If you're willing to entertain a change of shell, zsh has declare -U for this exact purpose: it'll automatically strip duplicates from an array while maintaining precedence. It also lets you use $path instead of $PATH.
% PATH=a:b:c:d:c:b:a
% echo $PATH
a:b:c:d:c:b:a
% declare -U PATH
% echo $PATH
or, for improved readability, you can use the array form, which is kept synchronized automatically:
% path=(a b c d c b a)
% print $path
a b c d c b a
% print $PATH
a:b:c:d:c:b:a
% declare -U path
% print $path
a b c d
% print $PATH
a:b:c:d
My approach is like rcollyer's, but more universal, deals with precedence and uses much more code.
function append () {
local val
eval val=\$$1
if [[ x$val = x ]] ; then
eval $1=$2
else
eval $1="$val:$2"
fi
}
function is_in() {
local pattern
pattern=":$1\$|^$1:|:$1:"
echo $2 | egrep -q "$pattern" && return 0
return 1
}
function append_if_absent() {
local val
eval val=\$$1
if ! is_in "$2" "$val" ; then
append "$1" "$2"
fi
}
export ROOTSYS=/usr/local/root
append_if_absent LD_LIBRARY_PATH $ROOTSYS/lib/root
append_if_absent PATH $ROOTSYS/bin
My solution is the one liner:
export PATH=`echo :<new path>:${PATH} | sed -e 's/\:/\n/g' | sort | uniq \
| awk 'BEGIN {ORS=":"} {print $0}'`
where sed replaces : with a newline, sort and uniq strip out any duplicates, and awk rebuilds the path. This does have the disadvantage that the order is not maintained, so if you want to have programs in one location to have precedence over ones in other locations, this will cause problems. I haven't implemented it, but I imagine you could use perl to maintain these in order while stripping out the duplicates.
Here is what I have been using for a long time: I added a function called addpath() to .bash_profile, or .bashrc and then I can add a directory to the path knowing there will be no duplicate. For example:
addpath ~/bin
addpath ~/myproj/bin
Here is the source for addpath():
function addpath()
{
if [ $# -eq 0 ]
then
echo " Usage: addpath dir ..."
return 1
fi
local p
local dir
local IFS=:
local found
for dir; do
found=0
for p in $PATH; do
if [ "$p" = "$dir" ]; then
found=1
fi
done
if [ "_$found" = "_0" ]; then
PATH=$PATH:$dir
fi
done
}
From my .bashrc:
pathadd() {
if [ -d "$1" ] && [[ ":$PATH:" != *":$1:"* ]]; then
PATH="$PATH:$1"
fi
}
pathadd /usr/local/bin
pathadd ~/bin
...etc
Note that this adds directories to the end of the path; make the obvious change to add them to the beginning.