Passing 'out' variables to function - fish

I'm trying to make my fish_prompt fancier by having the colors of my username and host change depending on what they are. However, if this attempt fails, I'd rather leave the colors as defaults of $fish_color_[host|user] So far, this works:
function fish_prompt
# …
set -l color_host $fish_color_host
if command -qs shasum
set color_host (colorize (prompt_hostname))
end
# …
echo -n -s \
(set_color --bold $color_user) "$USER" \
(set_color normal) # \
(set_color $color_host) (prompt_hostname) \
' ' \
(set_color --bold $color_cwd) (prompt_pwd) \
(set_color normal) " $suffix "
end
function colorize
echo -n (printf "$argv" | shasum -ta 256 - | cut -c 59-64 | tr -d '\n')
end
At this point, I thought that this would look a lot cleaner if the shasum check were in colorize instead of being repeated in fish_prompt. I then tried this, figuring I might be able to pass the variable name if I single quoted it:
function fish_prompt
# …
set -l color_user $fish_color_user
colorize_more 'color_user' "$USER"
# …
end
function colorize_more -a what -a whence
if command -qs shasum
set "$what" (printf "$whence" | shasum -ta 256 - | cut -c 59-64 | tr -d '\n')
end
end
However, this doesn't appear to change $color_user in fish_prompt. I suspect that's because the set in colorize_more can't modify a variable that's local to the one in fish_prompt, as the color that should come out of colorize_more is a yellow instead of the green that I get from $fish_color_user.
How can I restructure my fish_prompt and accessory function(s) so that I have a colorize function that sets a variable to some value only if it is able to?

There are multiple possibilities here.
One is the "--no-scope-shadowing" flag to function
function colorize_more -a what -a whence --no-scope-shadowing
that will keep the outer local scope visible. This won't work with e.g. $argv, and if you need any variables they could potentially overwrite externally defined ones, so this is to be used with care.
Another is to define the variables as global instead - this has the disadvantage that they'll then stay defined and will conflict with any global variable of the same name.
Another is to output the value instead of assigning it.
E.g.
function colorize_more -a whence
if command -qs shasum
echo (printf "$whence" | shasum -ta 256 - | cut -c 59-64 | tr -d '\n')
end
end
set -l color_user (colorize_more $USER)
set -q color_user[1]; or set color_user $fish_color_user
# or you could let colorize_more output $fish_color_user if it wouldn't output anything else

Inspired by faho's comment of "Another is to output the value instead of assigning it", I ended up doing this:
function fish_prompt --description 'Write out the prompt'
# …
set -l color_host (colorize (prompt_hostname) "$fish_color_host")
set -l color_user (colorize "$USER" "$fish_color_user")
# …
end
function colorize -a what -a default -d 'Outputs a hex color for a string or, failing that, a default'
if command -qs shasum
echo -n (printf "$what" | shasum -ta 256 - | cut -c 59-64 | tr -d '\n')
else
echo "$default"
end
end

Related

Sh script to output unused interfaces on linux

I asked the dark side and here's what it printed.....
#!/bin/bash
for interface in $(ip addr show | awk '/^[0-9]+:/ {print $2}' | tr -d :)
do
if ! ip addr show $interface | awk '/inet / {print $2}' | grep -q . ; then
echo $interface
fi
done
I want to add n+ variable directly so the output will be the interfaces that is not used by the system,
Done
I have these two scripts, one for Linux and one for Mac, I hope they serve you because I have tested them on Linux Ubuntu and Mac.
on linux
#!/bin/bash
# This script will output unused interfaces on Linux
# Get list of all interfaces
interfaces=$(ifconfig -a | grep -o '^[^ ]*:' | tr -d :)
# Loop through each interface
for interface in $interfaces; do
# Check if interface is up
if [[ $(ifconfig $interface | grep -c 'UP') -eq 0 ]]; then
# Output interface name
echo "$interface is unused"
fi
done
on mac
#!/bin/bash
# Get list of all network interfaces
interfaces=$(networksetup -listallnetworkservices | tail -n +2)
# Loop through each interface
for interface in $interfaces; do
# Get the IP address of the interface
ip=$(ipconfig getifaddr "$interface")
# If the IP address is empty, the interface is unused
if [ -z "$ip" ]; then
echo "$interface is unused"
fi
done
#!/bin/bash
count=0
for interface in $(ip addr show | awk '/^[0-9]+:/ {print $2}' | tr -d :)
do
if ! ip addr show $interface | awk '/inet / {print $2}' | grep -q . ; then
free_interfaces[$count]=$interface
count=$((count + 1))
fi
done
case $count in
0)
echo "No usable interface found."
exit 1
;;
1)
DEFIF=${free_interfaces[0]}
echo "The interface $DEFIF will be used."
;;
*)
echo "Available interfaces to select: "
PS3="Press a number to select an interface to use (1-$count): "
select interface in "${free_interfaces[#]}"; do
DEFIF=$interface
break
done
echo "The interface $DEFIF will be used."
;;
esac

Bash or Python efficient substring matching and filtering

I have a set of filenames in a directory, some of which are likely to have identical substrings but not known in advance. This is a sorting exercise. I want to move the files with the maximum substring ordered letter match together in a subdirectory named with that number of letters and progress to the minimum match until no matches of 2 or more letters remain. Ignore extensions. Case insensitive. Ignore special characters.
Example.
AfricanElephant.jpg
elephant.jpg
grant.png
ant.png
el_gordo.tif
snowbell.png
Starting from maximum length matches to minimum length matches will result in:
./8/AfricanElephant.jpg and ./8/elephant.jpg
./3/grant.png and ./3/ant.png
./2/snowbell.png and ./2/el_gordo.tif
Completely lost on an efficient bash or python way to do what seems a complex sort.
I found some awk code which is almost there:
{
count=0
while ( match($0,/elephant/) ) {
count++
$0=substr($0,RSTART+1)
}
print count
}
where temp.txt contains a list of the files and is invoked as eg
awk -f test_match.awk temp.txt
Drawback is that a) this is hardwired to look for "elephant" as a string (I don't know how to make it take an input string (rather than file) and an input test string to count against, and
b) I really just want to call a bash function to do the sort as specified
If I had this I could wrap some bash script around this core awk to make it work.
function longest_common_substrings () {
shopt -s nocasematch
for file1 in * ; do for file in * ; do \
if [[ -f "$file1" ]]; then
if [[ -f "$file" ]]; then
base1=$(basename "$file" | cut -d. -f1)
base2=$(basename "$file1" | cut -d. -f1)
if [[ "$file" == "$file1" ]]; then
echo -n ""
else
echo -n "$file $file1 " ; $HOME/Scripts/longest_common_substring.sh "$base1" "$base2" | tr -d '\n' | wc -c | awk '{$1=$1;print}' ;
fi
fi
fi
done ;
done | sort -r -k3 | awk '{ print $1, $3 }' > /tmp/filesort_substring.txt
while IFS= read -r line; do \
file_to_move=$(echo "$line" | awk '{ print $1 }') ;
directory_to_move_to=$(echo "$line" | awk '{ print $2 }') ;
if [[ -f "$file_to_move" ]]; then
mkdir -p "$directory_to_move_to"
\gmv -b "$file_to_move" "$directory_to_move_to"
fi
done < /tmp/filesort_substring.txt
shopt -u nocasematch
where $HOME/Scripts/longest_common_substring.sh is
#!/bin/bash
shopt -s nocasematch
if ((${#1}>${#2})); then
long=$1 short=$2
else
long=$2 short=$1
fi
lshort=${#short}
score=0
for ((i=0;i<lshort-score;++i)); do
for ((l=score+1;l<=lshort-i;++l)); do
sub=${short:i:l}
[[ $long != *$sub* ]] && break
subfound=$sub score=$l
done
done
if ((score)); then
echo "$subfound"
fi
shopt -u nocasematch
Kudos to the original solution for computing the match in the script which I found elsewhere in this site

fish shell: Prompt changing to '>'

I recently switched to fish and modified one of the prompts available from fish_config to look like this.
function fish_greeting
fortune
end
function fish_prompt
echo
set -l retc brblack
test $status = 0; and set retc bryellow
set -q __fish_git_prompt_showupstream
or set -g __fish_git_prompt_showupstream auto
function _nim_prompt_wrapper
set retc $argv[1]
set cust $argv[4]
set field_name $argv[2]
set field_value $argv[3]
set_color normal
set_color $retc
echo
echo -n ' ├─'
echo -n '[ '
set_color normal
test -n $field_name
and echo -n $field_name
set_color -o brblack
echo -n ' ▶ '
set_color $retc
set_color $cust
echo -n $field_value
set_color $retc
echo -n ' ]'
end
set_color $retc
echo -n '─┬─'
echo -n '[ '
set_color -o red
echo -n (prompt_hostname)
echo -n ': '
if test "$USER" = root -o "$USER" = toor
set_color -o brred
else
set_color -o brwhite
end
echo -n $USER
set_color -o brblack
echo -n ' ▶ '
set_color -o brcyan
echo -n (pwd)
set_color $retc
echo -n ' ]'
# Virtual Environment
set -q VIRTUAL_ENV_DISABLE_PROMPT
or set -g VIRTUAL_ENV_DISABLE_PROMPT true
set -q VIRTUAL_ENV
and _nim_prompt_wrapper $retc '🐍 ' (basename "$VIRTUAL_ENV") cyan
# git
set prompt_git (fish_git_prompt | string trim -c ' ()')
test -n "$prompt_git"
and _nim_prompt_wrapper $retc (basename -s .git (git config --get remote.origin.url) 2> /dev/null) $prompt_git
# New line
echo
# Background jobs
set_color normal
for job in (jobs)
set_color $retc
echo -n ' │ '
set_color brown
echo $job
end
set_color normal
set_color $retc
echo -n ' ╰─> '
set_color normal
end
The general layout of my prompt:
─┬─[ hostname: user ▶ pwd ]
╰─> _
And blow is what I want instead of >:
─┬─[ hostname: user ]
├─[ pwd ]
╰─> _
OR
─┬─[ hostname: username ]
├─⎡ as_much_as_possible ⎤
├─⎣ the_rest_of_PWD ⎦
╰─> _
But, when $PWD is longer than the window's column size, the whole prompt is just >. I feel that using $COLUMNS should work, but I don't know how I can check the length of pwd before echoing it.
I DO NOT WANT TO USE prompt_pwd.
Thanks in advance! ;)
You can store whatever you want in a variable and then check it, modify it however you want and then echo it.
Here's a rough sketch:
set -l firstline '─┬─[' (prompt_hostname): $USER ▶ $PWD ']'
set -l secondline
if test (string length -- "$firstline") -gt $COLUMNS
# move $PWD to the second line
set firstline '─┬─[' (prompt_hostname): $USER ']'
set secondline '├─[' $PWD ']'
end
echo $firstline
echo $secondline
I DO NOT WANT TO USE prompt_pwd.
You very possibly do. It handles replacing $HOME with ~ (which saves quite a few columns) and does shortening to $fish_prompt_pwd_dir_length characters, or no shortening other than ~ if that's set to 0.
You can even adapt the shortening to $COLUMNS. From my prompt:
# Shorten pwd if prompt is too long
set -l pwd (prompt_pwd)
# 0 means unshortened
for i in $fish_prompt_pwd_dir_length 0 10 9 8 7 6 5 4 3 2 1
set pwd (fish_prompt_pwd_dir_length=$i prompt_pwd)
set -l len (string length -- $prompt_host_nocolor$pwd$last_status$delim' ')
if test $len -lt $COLUMNS
break
end
end

Capturing multiple line output into a Bash variable with busybox sh

I'm trying to convert a Debian Bash script into a linux Busybox sh script. I'm stuck trying to convert the following command:
read -r -d '' MESSAGE << EOM
Return code: $retn_code
Start of backup: $DATESTART
End of backup: $DATEEND
$(df -h | grep '/share/USB')
EOM
The problem is with the -d option of read that is not available with Busybox. How can I set a variable ($MESSAGE in this case) to a string with multiple lines that includes values from other variables?
The output MESSAGE is going in a log file and in a message sent by sendmail:
echo "RESULTS: $MESSAGE" >> $LOGFILE
sendmail -S smtp.server.com -f "$FROM" "$RECIPIENTS" <<EOF
subject:$SUBJECT
from:$FROM
$MESSAGE
EOF
Simplest answer is not to use read.
MESSAGE=$(cat <<EOM
Return code: $retn_code
Start of backup: $DATESTART
End of backup: $DATEEND
$(df -h | grep '/share/USB')
EOM
)
MESSAGE=$( printf "%s\n%s\n%s\n%s\n" \
"Return code: $retn_code" \
"Start of backup: $DATESTART" \
"End of backup: $DATEEND" \
"$(df -h | grep '/share/USB')" \
)
You don't need a special command in any shell; just a regular assignment.
message="Return code: $retn_code
Start of backup: $DATESTART
End of backup: $DATEEND
$(df -h | grep '/share/USB')
"

get list of sections from ini-file using shell (sed/awk)

I want to create a var from the section names of an ini file like:
[foo]
; ...
[bar]
; ...
[baz:bar]
;...
now I need a var like
SECTIONS="foo bar baz"
thanks in advance
One line solution could be:
export SECTIONS=`grep "^\[" test.ini |sort -u | xargs | tr '\[' ' ' | tr '\]' ' ' `
SECTIONS=$(crudini --get your.ini | sed 's/:.*//')
I'm now using this construct, don't need to know if a section exists. just read it, if it's empty it does not exist.
INI_FILE=test.ini
function ini_get
{
eval `sed -e 's/[[:space:]]*\=[[:space:]]*/=/g' \
-e 's/;.*$//' \
-e 's/[[:space:]]*$//' \
-e 's/^[[:space:]]*//' \
-e "s/^\(.*\)=\([^\"']*\)$/\1=\"\2\"/" \
< $INI_FILE \
| sed -n -e "/^\[$1\]/,/^\s*\[/{/^[^;].*\=.*/p;}"
echo ${!2}
}
IP=$(ini_get 50001 ip)
PORT=$(ini_get 50001 port)
echo $IP:$PORT