Git bash doesn't recognize 'rev' command - bash

I am trying to use a function that shortens my bash prompt. I have added it in .bash_profile:
function last_two_dirs {
pwd |rev| awk -F / '{print $1,$2}' | rev | sed s_\ _/_
}
export PS1='$(last_two_dirs) $(__git_ps1) ➡ '
But I get an error bash: rev: command not found everytime I launch git bash.
I have set the PATH correctly since other commands work correctly except rev. Is rev not part of git bash? Or is there any other way to show only the parent and the current directory for the bash prompt?
OS: Windows 10

Your environment doesn't seem to have the rev command. However, you don't need it, there are built-in facilities for what you want to do.
To get the current working directory in your PS1, use \w:
PS1='\w\$ '
This gets you the full path, so your prompt will look something like
~/tinker/so/subdir/subsubdir$
Now, set the $PROMPT_DIRTRIM variable to the number of trailing directories to retain:
PROMPT_DIRTRIM=2
This will get you a prompt like
~/.../subdir/subsubdir$

This is the code that worked for me -
PROMPT_COMMAND='case $PWD in
$HOME) HPWD="~";;
$HOME/*/*) HPWD="${PWD#"${PWD%/*/*}/"}";;
$HOME/*) HPWD="~/${PWD##*/}";;
/*/*/*) HPWD="${PWD#"${PWD%/*/*}/"}";;
*) HPWD="$PWD";;
esac'
PS1='$HPWD \$'
referred from link

bash function around $PWD
Under bash, there are lot of trick and features you could use to make this a lot quicker and efficient
Simply two last path level:
last_two_dirs() {
local left=${PWD%/*};
echo "${PWD#${left%/*}/}"
}
More complex: first and last level:
path_parts() {
local APATH
IFS=/ read -a APATH <<<"$PWD"
if ((${#APATH[#]}>3)) ;then
echo "/${APATH[1]}..${APATH[-1]}"
else
echo "$PWD"
fi
}
Another special case
Les imagine your path in this kind:
/srv/dist/invoices-2019/data-2019-02-10/seq-123
To trim all part of path until first dash:
path_dash_Trim () {
local APATH;
IFS=/ read -a APATH <<< "$PWD";
APATH="${APATH[*]#*-}";
echo "${APATH// /\/}"
}
will render
/srv/dist/2019/2019-02-10/123
Fork performance issue
In order to reduce performance issue, there is a proper way to eliminate forks ( var=$(commnand) ): Set variable in function:
Simply replace
echo ${PWD#${left%/*}/}
by
printf -v $1 %s "${PWD#${left%/*}/}"
or better:
printf -v ${1:-myResult} '%s' "${PWD#${left%/*}/}"
Sample:
last_two_dirs() { local left=${PWD%/*};printf -v $1 "%s" "${PWD#${left%/*}/}" ;}
last_two_dirs result
printf -v $1 %s "$result"
or
path_parts() {
local APATH
IFS=/ read -a APATH <<<"$PWD"
if ((${#APATH[#]}>3)) ;then
printf -v $1 %s "/${APATH[1]}..${APATH[-1]}"
else
printf -v $1 %s "$PWD"
fi
}
and so on...
Then
export -f path_parts
PROMPT_COMMAND='path_parts PS1&&PS1+=" \\$ "'
or even simply dedicated function:
myPrompt() {
local APATH fmt='%s\[\e];%s\a\] $ '
IFS=/ read -a APATH <<<"$PWD"
if ((${#APATH[#]}>4)) ;then
printf -v PS1 "$fmt" "/${APATH[1]}..${APATH[-2]}/${APATH[-1]}"{,}
else
printf -v PS1 "$fmt" "$PWD"{,}
fi
}
PROMPT_COMMAND=myPrompt

Install MSYS2 tools, it has the tools you need.

function last_two_dirs ()
{
awk -F/ '{print ((NF>1)?$(NF-1)"/":"")""$NF}' <<< $PWD
}

You can simply use
pwd | sed -r 's|.*/([^/]+/[^/]+)$|\1|'
instead

Related

How to use variable with awk when being read from a file

I have a file with the following entries:
foop07_bar2_20190423152612.zip
foop07_bar1_20190423153115.zip
foop08_bar2_20190423152612.zip
foop08_bar1_20190423153115.zip
where
foop0* = host
bar* = fp
I would like to read the file and create 3 variables, the whole file name, host and fp (which stands for file_path_differentiator).
I am using read to take the first line and get my whole file name variable, I though I could then feed this into awk to grab the next two variables, however the first method of variable insertion creates an error and the second gives me all the variables.
I would like to loop each line, as I wish to use these variables to ssh to the host and grab the file
#!/bin/bash
while read -r FILE
do
echo ${FILE}
host=`awk 'BEGIN { FS = "_" } ; { print $1 }'<<<<"$FILE"`
echo ${host}
path=`awk -v var="${FILE}" 'BEGIN { FS = "_" } ; { print $2 }'`
echo ${path}
done <zips_not_received.csv
Expected Result
foop07_bar2_20190423152612.zip
foop07
bar2
foop07_bar1_20190423153115.zip
foop07
bar1
Actual Result
foop07_bar2_20190423152612.zip
/ : No such file or directoryfoop07_bar2_20190423152612.zip
bar2 bar1 bar2 bar1
You can do this alone with bash, without using any external tool.
while read -r file; do
[[ $file =~ (.*)_(.*)_.*\.zip ]] || { echo "invalid file name"; exit 1; }
host="${BASH_REMATCH[1]}"
path="${BASH_REMATCH[2]}"
echo "$file"
echo "$host"
echo "$path"
done < zips_not_received.csv
typical...
Managed to work a solution after posting...
#!/bin/bash
while read -r FILE
do
echo ${FILE}
host=`echo "$FILE" | awk -F"_" '{print $1}'`
echo $host
path=`echo "$FILE" | awk -F"_" '{print $2}'`
echo ${path}
done <zips_not_received.csv
not sure on the elegance or its correctness as i am using echo to create variable...but i have it working..
Assuming there is no space or _ in your "file name" that are part of the host or path
just separate line before with sed, awk, ... if using default space separator (or use _ as argument separator in batch). I add the remove of empty line value as basic security seeing your sample.
sed 's/_/ /g;/[[:blank:]]\{1,\}/d' zips_not_received.csv \
| while read host path Ignored
do
echo "${host}"
echo "${path}"
done

Bash substitution giving basename with one leading directory component

For GNU Screen titling purposes, I'd like to get ahold of the current directory name prefixed by the name of its parent. For example, within directories
/home/rhys/share/pkgconfig
/home/rhys
/home
/
producing outputs
share/pkgconfig
home/rhys
home
/
In Bash, starting from a guess like
echo $(basename $(dirname $PWD))/$(basename $PWD)
one can arrive at a better solution
echo $(basename "${PWD%/*}")/${PWD##*/}
where I say better because two fewer processes are spawned.
Anyone have a cute trick to avoid using basename at all? This is for something run every shell prompt so it'd be nice to be as lightweight as possible.
for p in /home/rhys/share/pkgconfig /home/rhys /home /; do
[[ $p =~ .*/([^/]+/[^/]+)$ ]] && echo "${BASH_REMATCH[1]}" || echo "$p"
done
As a function:
last2() { [[ $1 =~ .*/([^/]+/[^/]+)$ ]] && echo "${BASH_REMATCH[1]}" || echo "$1"; }
Should work on bash >= 3.2
How about this, using bash and awk:
awk -F'/' '{print $(NF-1)"/"$NF}' <<<"$PWD"
Edit
The previous one is not quite right, as it prints /home rather than just home. Maybe you can live with that. If not, this one works fully:
awk -F'/' '{if (NF==2 && $2) {$0=$2} else {$0=$(NF-1)"/"$NF}}1' <<<"$PWD"
Testing it out:
awk -F'/' '{if (NF==2 && $2) {$0=$2} else {$0=$(NF-1)"/"$NF}}1' <<EOF
/home/rhys/share/pkgconfig
/home/rhys
/home
/
EOF
Output:
share/pkgconfig
home/rhys
home
/
Here's a function that will work in bash 4 or later:
trim () (
IFS=/
read -a c <<< "$1"
unset c[0]
fragment="${c[*]: -2:2}"
echo "${fragment:-/}"
)
for p in /home/rhys/share/pkgconfig /home/rhys /home /; do
trim "$p"
done
share/pkgconfig
home/rhys
home
/

how to prevent for loop from using space as deliminator, bash script

I am trying to right a bash script to do multiple checks and searches for a CMS my company uses. I trying to implement a function for a user to be able to search for a certain macro call and the function return all the files that contain the call, the line the macro is called on, and the actual code in the macro call. What I have seems to be getting screwed up by the fact I am using a for loop to format the output. Here's the snippet of the script I am working on:
elif [ "$choice" = "2" ]
then
echo -e "\n What macro call are we looking for $name?"
read macrocall
for i in $(grep -inR "$macrocall" $sitepath/templates/macros/); do
file=$(echo $i | cut -d\: -f1 | awk -F\/ '{ print $NF }')
line=$(echo $i | cut -d\: -f2)
calltext=$(echo $i | cut -d\: -f3-)
echo -e "\nFile: $file"
echo -e "\nLine: $line"
echo -e "\nMacro Call from file: $calltext"
done
fi
the current script runs the first few fields until it gets a a space and then everything gets all screwy. Anybody have any idea how I can have the for loops deliminator to be each result of the grep? any suggestions would be helpful. Let me know if any of you need more info. Thanks!
The right way to do this would be more like:
printf "\n What macro call are we looking for %s?" "$name"
read macrocall
# ensure globbing is off and set IFS to a newline after saving original values
oSET="$-"; set -f; oIFS="$IFS"; IFS=$'\n'
awk -v macrocall="$macrocall" '
BEGIN { lc_macrocall = "\\<" tolower(macrocall) "\\>" }
tolower($0) ~ lc_macrocall {
file=FILENAME
sub(/.*\//,"",file)
printf "\n%s\n", file
printf "\n%d\n", FNR
printf "\nMacro Call from file: %s\n", $0
}
' $(find "$sitepath/templates/macros" -type f -print)
# restore original IFS and globbing values
IFS="$oIFS"; set +f -"$oSET"
This solves the problem of having spaces in your file names as originally requested, but also handles globbing characters in your file names, and the various typical echo issues.
You can set the internal field separator $IFS (which is normally set to space, tab and newline) to just newline to get around this problem:
IFS="\n"

Modify config file using bash script

I'm writing a bash script to modify a config file which contains a bunch of key/value pairs. How can I read the key and find the value and possibly modify it?
A wild stab in the dark for modifying a single value:
sed -c -i "s/\($TARGET_KEY *= *\).*/\1$REPLACEMENT_VALUE/" $CONFIG_FILE
assuming that the target key and replacement value don't contain any special regex characters, and that your key-value separator is "=". Note, the -c option is system dependent and you may need to omit it for sed to execute.
For other tips on how to do similar replacements (e.g., when the REPLACEMENT_VALUE has '/' characters in it), there are some great examples here.
Hope this helps someone. I created a self contained script, which required config processing of sorts.
#!/bin/bash
CONFIG="/tmp/test.cfg"
# Use this to set the new config value, needs 2 parameters.
# You could check that $1 and $2 is set, but I am lazy
function set_config(){
sudo sed -i "s/^\($1\s*=\s*\).*\$/\1$2/" $CONFIG
}
# INITIALIZE CONFIG IF IT'S MISSING
if [ ! -e "${CONFIG}" ] ; then
# Set default variable value
sudo touch $CONFIG
echo "myname=\"Test\"" | sudo tee --append $CONFIG
fi
# LOAD THE CONFIG FILE
source $CONFIG
echo "${myname}" # SHOULD OUTPUT DEFAULT (test) ON FIRST RUN
myname="Erl"
echo "${myname}" # SHOULD OUTPUT Erl
set_config myname $myname # SETS THE NEW VALUE
Assuming that you have a file of key=value pairs, potentially with spaces around the =, you can delete, modify in-place or append key-value pairs at will using awk even if the keys or values contain special regex sequences:
# Using awk to delete, modify or append keys
# In case of an error the original configuration file is left intact
# Also leaves a timestamped backup copy (omit the cp -p if none is required)
CONFIG_FILE=file.conf
cp -p "$CONFIG_FILE" "$CONFIG_FILE.orig.`date \"+%Y%m%d_%H%M%S\"`" &&
awk -F '[ \t]*=[ \t]*' '$1=="keytodelete" { next } $1=="keytomodify" { print "keytomodify=newvalue" ; next } { print } END { print "keytoappend=value" }' "$CONFIG_FILE" >"$CONFIG_FILE~" &&
mv "$CONFIG_FILE~" "$CONFIG_FILE" ||
echo "an error has occurred (permissions? disk space?)"
sed "/^$old/s/\(.[^=]*\)\([ \t]*=[ \t]*\)\(.[^=]*\)/\1\2$replace/" configfile
So I can not take any credit for this as it is a combination of stackoverflow answers and help from irc.freenode.net #bash channel but here are bash functions now to both set and read config file values:
# https://stackoverflow.com/a/2464883
# Usage: config_set filename key value
function config_set() {
local file=$1
local key=$2
local val=${#:3}
ensureConfigFileExists "${file}"
# create key if not exists
if ! grep -q "^${key}=" ${file}; then
# insert a newline just in case the file does not end with one
printf "\n${key}=" >> ${file}
fi
chc "$file" "$key" "$val"
}
function ensureConfigFileExists() {
if [ ! -e "$1" ] ; then
if [ -e "$1.example" ]; then
cp "$1.example" "$1";
else
touch "$1"
fi
fi
}
# thanks to ixz in #bash on irc.freenode.net
function chc() { gawk -v OFS== -v FS== -e 'BEGIN { ARGC = 1 } $1 == ARGV[2] { print ARGV[4] ? ARGV[4] : $1, ARGV[3]; next } 1' "$#" <"$1" >"$1.1"; mv "$1"{.1,}; }
# https://unix.stackexchange.com/a/331965/312709
# Usage: local myvar="$(config_get myvar)"
function config_get() {
val="$(config_read_file ${CONFIG_FILE} "${1}")";
if [ "${val}" = "__UNDEFINED__" ]; then
val="$(config_read_file ${CONFIG_FILE}.example "${1}")";
fi
printf -- "%s" "${val}";
}
function config_read_file() {
(grep -E "^${2}=" -m 1 "${1}" 2>/dev/null || echo "VAR=__UNDEFINED__") | head -n 1 | cut -d '=' -f 2-;
}
at first I was using the accepted answer's sed solution: https://stackoverflow.com/a/2464883/2683059
however if the value has a / char it breaks
in general it's easy to extract the info with grep and cut:
cat "$FILE" | grep "^${KEY}${DELIMITER}" | cut -f2- -d"$DELIMITER"
to update you could do something like this:
mv "$FILE" "$FILE.bak"
cat "$FILE.bak" | grep -v "^${KEY}${DELIMITER}" > "$FILE"
echo "${KEY}${DELIMITER}${NEWVALUE}" >> "$FILE"
this would not maintain the order of the key-value pairs obviously. add error checking to make sure you don't lose your data.
I have done this:
new_port=$1
sed "s/^port=.*/port=$new_port/" "$CONFIG_FILE" > /yourPath/temp.x
mv /yourPath/temp.x "$CONFIG_FILE"
This will change port= to port=8888 in your config file if you choose 8888 as $1 for example.
Suppose your config file is in below format:
CONFIG_NUM=4
CONFIG_NUM2=5
CONFIG_DEBUG=n
In your bash script, you can use:
CONFIG_FILE=your_config_file
. $CONFIG_FILE
if [ $CONFIG_DEBUG == "y" ]; then
......
else
......
fi
$CONFIG_NUM, $CONFIG_NUM2, $CONFIG_DEBUG is what you need.
After your read the values, write it back will be easy:
echo "CONFIG_DEBUG=y" >> $CONFIG_FILE

Why is my awk print not showing up on the terminal

I have the following script which does a "which -a" on a command then a "ls -l" to let me know if it's a link or not .. ie "grep" since I have gnu commands installed (Mac with iTerm).
#!/usr/bin/env bash
which -a $1 | xargs -I{} ls -l "{}" \
| awk '{for (i = 1; i < 9; i++) $i = ""; sub(/^ */, ""); print}'
When I run this from the script "test grep" I receive no output, but when I run it via "bash -x test grep" I receive the following:
bash -x test grep
+ which -a grep
+ xargs '-I{}' ls -l '{}'
+ awk '{for (i = 1; i < 9; i++) $i = ""; sub(/^ */, ""); print}'
/usr/local/bin/grep -> ../Cellar/grep/3.1/bin/grep
/usr/bin/grep
The last 2 lines is what I'm looking to display. Thought this would be easier to do ;-) .. I also tried appending the pipe thinking printf would fix the issue:
| while read path
do
printf "%s\n" "$path"
done
Thanks and .. Is there a better way to get what I need?
The problem is that you named your script test.
If you want to run a command that's not in your PATH, you need to specify the directory it's in, e.g. ./test.
You're not getting an error for trying to run test because there is a built-in bash command called test that is used instead. For extra confusion, the standard test produces no output.
In conclusion:
Use ./ to run scripts in the current directory.
Never call your test programs test.
Thanks for the never naming a script "test" .. old habits are hard to break (I came from a non-unix background.
I ended with the following
for i in $(which -a $1)
do
stat $i | awk NR==1{'$1 = ""; sub(/^ */, ""); print}'
done
or simpler
for i in $(which -a $1)
do
stat -c %N "$i"
done
Consider the following shell function:
cmdsrc() {
local cmd_file cmd_file_realpath
case $(type -t -- "$1") in
file) cmd_file=$(type -P -- "$1")
if [[ -L "$cmd_file" ]]; then
echo "$cmd_file is a symlink" >&2
elif [[ -f "$cmd_file" ]]; then
echo "$cmd_file is a regular file" >&2
else
echo "$cmd_file is not a symlink or a regular file" >&2
fi
cmd_file_realpath=$(readlink -- "$cmd_file") || return
if [[ $cmd_file_realpath != "$cmd_file" ]]; then
echo "...the real location of the executable is $cmd_file_realpath" >&2
fi
;;
*) echo "$1 is not a file at all: $(type -- "$1")" >&2 ;;
esac
}
...used as such:
$ cmdsrc apt
/usr/bin/apt is a symlink
...the real location of the executable is /System/Library/Frameworks/JavaVM.framework/Versions/A/Commands/apt
$ cmdsrc ls
/bin/ls is a regular file
$ cmdsrc alias
alias is not a file at all: alias is a shell builtin
Took some suggestions and came up with the following:
The prt-underline is just a fancy printf function. I decided not to go with readline since the ultimate command resolution may be unfamiliar to me and I only deal with regular files .. so does't handle every situation but in the end gives me the output I was looking for. Thanks for all the help.
llt ()
{
case $(type -t -- "$1") in
function)
prt-underline "Function";
declare -f "$1"
;;
alias)
prt-underline "Alias";
alias "$1" | awk '{sub(/^alias /, ""); print}'
;;
keyword)
prt-underline "Reserved Keyword"
;;
builtin)
prt-underline "Builtin Command"
;;
*)
;;
esac;
which "$1" &> /dev/null;
if [[ $? = 0 ]]; then
prt-underline "File";
for i in $(which -a $1);
do
stat "$i" | awk 'NR==1{sub(/^ File: /, ""); print}';
done;
fi
}

Resources