How to shorten path parts between // in bash - bash

I want my bash prompt paths to be shortened:
~/workspace/project/my-project
# Should be
~/w/p/my-project
This could be achieved by just shortening parts of the path string between // to just the first character.
Is there a way to do this for example in sed?
edit:
Thought someone else looking into this might find what I ended useful so I'm editing it here.
.bashrc:
dir_chomp () {
pwd | sed "s|^$HOME|~|" 2> /dev/null | sed 's:\(\.\?[^/]\)[^/]*/:\1/:g'
}
parse_git_branch() {
git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/ (\1)/'
}
export PS1="\[\033[32m\]\$(dir_chomp)\[\033[33m\]\$(parse_git_branch)\[\033[00m\] $ "
prompt examples (coloring doesn't show):
~/w/e/coolstuff (master) $
~/.c/A/Cache $

If you want to unconditionally shorten all path components, you can do it quite easily with sed:
sed 's:\([^/]\)[^/]*/:\1/:g'
If you want to also insert ~ at the beginning of paths which start with $HOME, you can add that to the sed command (although this naive version assumes that $HOME does not include a colon).
sed 's:^'"$HOME"':~:/;s:\([^/]\)[^/]*/:\1/:g'
A better solution is to use bash substitution:
short_pwd() {
local pwd=$(pwd)
pwd=${pwd/#$HOME/\~}
sed 's:\([^/]\)[^/]*/:\1/:g' <<<"$pwd"
}
With that bash function, you can then "call" it from your PS1 string:
$ PS1='$(short_pwd)\$ '
~/s/tmp$ PS1='\$ '
$

Use PROMPT_COMMAND to set your prompt dynamically each time it is displayed.
shorten_path () {
cwd=${PWD/workspace/w}
cwd=${cwd/project/p}
cwd=${cwd/$HOME/~}
PS1="$cwd "'\$ '
}
PROMPT_COMMAND=shorten_path
This replaces the use of \w escape with custom code to shorten the current working directory. It has the unfortunate side effect of replacing ~ with the name of your home directory, though, which is why the third line is necessary to put it back, if desired.

I use this to shorten to 3 caracters plus "..":
shortpath()
{
dir=${1%/*} && last=${1##*/}
res=$(for i in ${dir//\// } ; do echo -n "${i:0:3}../" ; done)
echo "/$res$last"
}
Version to short to one caracter:
shortpath()
{
dir=${1%/*} && last=${1##*/}
res=$(for i in ${dir//\// } ; do echo -n "${i:0:1}/" ; done)
echo "/$res$last"
}
And then:
export PS1="\$(shortpath \$(pwd)) $"

Related

grep of string that includes a tilde returns non-matching lines

I'm trying to search for a specific string in a file. The string includes a tilde- I am trying to isolate the line that contains the string "~ ca_cert".
This is my script:
#!/bin/bash
LIST=("~ ca_cert" "backup_window")
FILE=./test
for x in "${LIST[#]}"; do
grep $x $FILE
done
When I run it, it returns other lines that contain tildes. For example, in a file that contains the following, it return all of the lines, when my intention is for it to only return the bottom line that contains ~ ca_cert:
./test:./terraform.tfplan: ~ update in-place
./test:./terraform.tfplan: ~ resource "aws_db_instance" "rds_instance" {
./test:./terraform.tfplan: ~ ca_cert_identifier = "rds-ca-2019" -> "rds-ca-2015"
Problem is not quoting pattern i.e. $x in your grep command. That basically runs your command as grep '~' ca_cert ./test and finds all the lines matching ~ with an error.
However you don't really need to run a loop here. Just use grep -f with process substitution:
grep -Ff <(printf '%s\n' "${LIST[#]}") ./test
./terraform.tfplan: ~ ca_cert_identifier = "rds-ca-2019" -> "rds-ca-2015"

How can I accept unquoted strings containing backslashes?

I want a command to convert from windows to unix filenames, simply to replace backslashes with frontslashes... but without quoting the argument with "" because that's a chore when copy-pasting.
It works in the other direction (u2w) with the input quoted and without, but not for w2u.
machine:~/glebbb> w2u "a\b\c"
a/b/c
machine:~/glebbb> w2u a\b\c
abc
How can I make it work? I tried every form of escaping, echo -E, printf etc, nothing seems to work!
function w2u {
if [ -z "$1" ] ; then
echo "w2u: must provide path to convert!"
return 1
else
printf "\n%s\n\n" "$1" | sed -e 's#\\#\/#g'
return 0
fi
}
If you're copy-pasting and the path is contained in the X clipboard, you can use xclip:
xclip -o | sed -e 's#\\#\/#g'
If you've got a ton of file paths to convert, you can process the whole file instead:
sed ... < file
will produce a new stream with the backslashes changed to slashes.
Otherwise I can't think of any way how to not-escape the parameters to w2u and yet have backslashes lose their meaning.

Replace an unknown string within a line using Bash

I want to be able to modify db001 with a string I pass into the command via CLI. At any given time db001 could be a different value so I can't just look for that value.
./myscript modify_db <new value>
myfile.txt
./myscript modify_db mynewdbname002
Before: database_node=db001.mydomain.local
After: database_node=mynewdbname002.mydomain.local
./myscript modify_db db003
Before: database_node=mynewdbname002.mydomain.local
After: database_node=db003.mydomain.local
You can use this sed command inside your script:
sed "s/^\(database_node=\)[^.]*/\1$1/" file
Example:
s='database_node=db001.mydomain.local'
repl() {
sed "s/^\(database_node=\)[^.]*/\1$1/" <<< "$s";
}
and call it as:
repl mynewdbname002
database_node=mynewdbname002.mydomain.local
repl db003
database_node=db003.mydomain.local
You could have a script like, just like below taking an input argument having the replacement value,
#!/bin/bash
perl -lpe "s/database_node=(\w+)/database_node=$1/g" file
and just do
./script.sh newdbname
Use the -i flag for in-place replacement and -i.bak for in-place replacement with a backup of your original file
perl -lpe -i.bak "s/database_node=(\w+)/database_node=$1/g" file
(or) with a simple bash function
function replaceFile() {
perl -lpe -i.bak "s/database_node=(\w+)/database_node=$1/g" file
}
I would avoid trying to produce the new state from the previous state and rather just use a template :
function modify_db() {
echo "database_node=$1.mydomain.local"
}
I use echo here for illustration but you should obviously do whatever you want to do with the "database_node=$1.mydomain.local".
Supposing it should modify the only line starting with database_node from a file db_conf after having printed the old value :
function modify_db() {
echo "Before: $(grep '^database_node=' db_conf)"
sed -i "s/^database_node=.*\.mydomain\.local/database_node=$1.mydomain.local/" db_conf
echo "After: $(grep '^database_node=' db_conf)"
}

bash: how can I read text in file into string, modify it and save it another file?

I have a file (imaginary):
test() {
echo ${%%NUMBER%%}
}
I need:
Read it into string variable
Replace %%NUMBER%% with number
Save it to another file, maintaining the multi-line architecture.
How I do it:
#!/bin/bash
# Full path of this script
FILE=`readlink -f "${BASH_SOURCE[0]}"`
# This directory path
DIR=`dirname "${FILE}"`
repl() {
STRING=$(cat $DIR/skel.txt)
STRING=$(echo $STRING | sed "s/%%NUMBER%%/$1/")
echo $STRING
}
# try it
repl 50 > ./out.sh
# tried as well:
# repl 50 | tee ./out.sh
# echo "$(repl 50)" > ./out.sh
# STRING=$(echo -e $STRING | sed "s/%%NUMBER%%/$1/")
But I always get everything in one line in out.sh file. Need it to stay multiline, as in source.
Thanks.
Try this:
repl() {
sed "s/%%NUMBER%%/$1/" "$DIR/skel.txt"
}
This is far more efficient, and won't eat your precious newlines.

How do I edit the output of a bash script before executing it?

For example look at the following line of bash-code
eval `echo "ls *.jpg"`
It lists all jpgs in the current directory. Now I want it to just print the line to the prompt so I can edit it before executing. (Like key-up does for example)
How do I do that?
The reason for this question comes from a much more usefull alias:
alias ac="history 2 | sed -n '1 s/[ 0-9]*//p' >> ~/.commands; sort -fu ~/.commands > ~/.commandsTmp; mv ~/.commandsTmp ~/.commands"
alias sc='oldIFS=$IFS; IFS=$'\n'; text=(); while read line ; do text=( ${text[#]-} "${line}") ; done < ~/.commands; PS3="Choose command by number: " ; eval `select selection in ${text[#]}; do echo "$selection"; break; done`; IFS=$oldIFS'
alias rc='awk '"'"'{print NR,$0}'"'"' ~/.commands; read -p "Remove number: " number; sed "${number} d" ~/.commands > ~/.commandsTmp; mv ~/.commandsTmp ~/.commands'
Where ac adds or remembers the last typed command, sc shows the available commands and executes them and rc deletes or forgets a command. (You need to touch ~/.commands before it works)
It would be even more usefull if I could edit the output of sc before executing it.
history -s whatever you want
will append "whatever you want" to your bash history. Then a simple up arrow (or !! followed by enter if you have shopt histreedit enabled --- I think that's the option I'm thinking of, not 100% sure), will give you "whatever you want" on the command line, ready to be edited.
Some comments on your aliases:
Simplified quoting:
alias rc='awk "{print NR,\$0}" ~/.commands ...'
No need for tail and you can combine calls to sed:
alias ac="history 2 | sed -n '1 s/[ 0-9]*//p'..."
Simplified eval and no need for $IFS:
alias sc='text=(); while read line ; do text+=("${line}") ; done < ~/.commands; PS3="Choose command by number: " ; select selection in "${text[#]}"; do eval "$selection"; break; done'
#OP, you should really put those commands into subroutines, and when you want to use them, source it. (taken from dennis's answers)
rc(){
awk "{print NR,\$0}" ~/.commands ...
}
ac(){
history 2 | sed -n '1 s/[ 0-9]*//p'...
}
sc(){
text=()
while read line
do
text+=("${line}")
done < ~/.commands
PS3="Choose command by number: "
select selection in "${text[#]}"
do
eval "$selection"
break
done
}
then save it as "library.sh" or something and when you want to use it
$ source /path/to/library.sh
Or
$ . /path/to/library.sh
Maybe you could use preexec.bash?
http://www.twistedmatrix.com/users/glyph/preexec.bash.txt
(On a related note, you can edit the current command line by using ctrl-x-e as well!)
cheers,
tavod

Resources