#!/bin/bash # Begin profile # ------------------------------------------------------------------------------ # Initial version from Beyond Linux From Scratch by # * James Robertson # * Dagmar d'Surreal # ------------------------------------------------------------------------------ # Current version from Geoffray Levasseur # 16/02/2013 v1.0.0 : Initial version # 24/10/2015 v2.0.0 : Added advanced functionnalities (clean, srr, etc.) # 04/02/2017 v2.0.1 : clean improvements (--shell) # 16/09/2018 v2.1.0 : Added rmhost, setc, setfr, more locales management # 23/09/2019 v2.1.1 : [bugfix] dpkgs # 24/09/2019 v2.1.2 : [bugfix] bug in profile version display # 16/12/2019 v2.2.0 : Added showinfo, primary write of showdiskmap # 08/01/2020 v2.3.0 : Added use of figlet and neofetch as a motd replace # 16/01/2020 v2.3.1 : [bugfix] non-interactive were blocked with some functions # 31/01/2020 v2.3.2 : Figlet: changed default font to ansi_shadow # 02/03/2020 v2.4.0 : Added command auzip # 03/03/2020 v2.5.0 : Added command taz and rmspc, auzip => utaz improved # 05/03/2020 v2.5.1 : Language consistancy fix, added pigz support in taz # 06/03/2020 v2.5.2 : Few aliases sorted out # 11/09/2020 v2.5.3 : Few more aliases, improved code consistancy and typo, # : improved utaz, removed showdiskmap, removed remaining French, # : added license information for future publication # 24/10/2020 v2.6.0 : Added session save and restore for Konsole # 25/12/2020 v2.6.1 : Add check on rmhost, improvements rmspc, created expendlist # 26/02/2021 v2.6.2 : [bugfix] taz: corrected bug with trailing slash on directories # 18/10/2021 v2.6.3 : changed PS1 for status bar style version, few minor improvements # 21/06/2022 v2.7.0 : added isipv4 and isipv6, use it in rmhost as an improvement # 22/06/2022 v2.7.1 : [bugfix] few minor corrections, added help command # 24/06/2022 v2.8.0 : Added backtrace, error and settrace, corrected showinfo # ------------------------------------------------------------------------------ # Copyright (c) 2013-2022 Geoffray Levasseur # Protected by the BSD3 license. Please read bellow for details. # # * Redistribution and use in source and binary forms, # * with or without modification, are permitted provided # * that the following conditions are met: # * # * Redistributions of source code must retain the above # * copyright notice, this list of conditions and the # * following disclaimer. # * # * Redistributions in binary form must reproduce the above # * copyright notice, this list of conditions and the following # * disclaimer in the documentation and/or other materials # * provided with the distribution. # * # * Neither the name of the copyright holder nor the names # * of any other contributors may be used to endorse or # * promote products derived from this software without # * specific prior written permission. # * # * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND # * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, # * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES # * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR # * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, # * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, # * OF SUCH DAMAGE. # ------------------------------------------------------------------------------ export PROFVERSION="2.8.0" export DEFAULT_CITY="Toulouse" # ------------------------------------------------------------------------------ # path* : private functions for PATH variable management # ------------------------------------------------------------------------------ pathremove () { local ifs=':' local newpath local dir local pathvar=${2:-PATH} for dir in ${!pathvar} ; do if [ "$dir" != "$1" ] ; then newpath=${newpath:+$newpath:}$dir fi done export $pathvar="$newpath" } pathprepend () { pathremove $1 $2 local pathvar=${2:-PATH} export $pathvar="$1${!pathvar:+:${!pathvar}}" } pathappend () { pathremove $1 $2 local pathvar=${2:-PATH} export $pathvar="${!pathvar:+${!pathvar}:}$1" } # ------------------------------------------------------------------------------ # expandlist : treat wildcards in a file/directory list # ------------------------------------------------------------------------------ expandlist() { local result="" for item in "$1"; do for content in "$item"; do result+="\"$content\" " done done echo $result } # ------------------------------------------------------------------------------ # timer_* functions : internal timing function for prompt # ------------------------------------------------------------------------------ function timer_now { date +%s%N } function timer_start { timer_start=${timer_start:-$(timer_now)} } function timer_stop { local delta_us=$((($(timer_now) - $timer_start) / 1000)) local us=$((delta_us % 1000)) local ms=$(((delta_us / 1000) % 1000)) local s=$(((delta_us / 1000000) % 60)) local m=$(((delta_us / 60000000) % 60)) local h=$((delta_us / 3600000000)) # Goal: always show around 3 digits of accuracy if ((h > 0)); then timer_show=${h}h${m}m elif ((m > 0)); then timer_show=${m}m${s}s elif ((s >= 10)); then timer_show=${s}.$((ms / 100))s elif ((s > 0)); then timer_show=${s}.$(printf %03d $ms)s elif ((ms >= 100)); then timer_show=${ms}ms elif ((ms > 0)); then timer_show=${ms}.$((us / 100))ms else timer_show=${us}us fi unset timer_start } # ------------------------------------------------------------------------------ # Function triguered internaly by bash : defining prompt # ------------------------------------------------------------------------------ set_prompt () { Last_Command=$? # Must come first! Blue='\[\e[0;34m\]' White='\[\e[01;37m\]' Yellow='\[\e[01;93m\]' Red='\[\e[01;31m\]' Green='\[\e[01;32m\]' OnGrey='\[\e[47m\]' OnRed='\[\e[41m\]' OnBlue='\[\e[44m\]' ICyan='\[\e[0;96m\]' Default='\[\e[00m\]' FancyX='\342\234\227' Checkmark='\342\234\223' # Begin with time PS1="\[\e[s$Blue$OnGrey [ \t ] $OnBlue" # Add a bright white exit status for the last command # If it was successful, print a green check mark. Otherwise, print # a red X. if [[ $Last_Command == 0 ]]; then PS1+="$White$OnBlue [ \$Last_Command " PS1+="$Green$Checkmark " else PS1+="$White$OnRed [ \$Last_Command " PS1+="$Yellow$FancyX " fi # Add the ellapsed time and current date timer_stop PS1+="($timer_show)$White ] $OnBlue " # If root, just print the host in red. Otherwise, print the current user # and host in green. if [[ $EUID -eq 0 ]]; then PS1+="$Red\\u$Green@\\h" else PS1+="$Green\\u@\\h" fi PS1+="\e[K\e[u$Default\n" # Print the working directory and prompt marker in blue, and reset # the text color to the default. PS1+="$ICyan\\w \\\$$Default " } # ------------------------------------------------------------------------------ # Show profile version # ------------------------------------------------------------------------------ ver () { echo "Profile version $PROFVERSION." } export -f ver # ------------------------------------------------------------------------------ # Determine if parameter is a valid IPv4 address # ------------------------------------------------------------------------------ isipv4 () { # Set up local variables local ip=$1 # Start with a regex format test if [[ $ip =~ ^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$ ]]; then local old_ifs=$IFS IFS="." ip=($ip) IFS=$old_ifs if [[ ${ip[0]} -le 255 && ${ip[1]} -le 255 \ && ${ip[2]} -le 255 && ${ip[3]} -le 255 ]]; then if [[ -t 1 ]]; then echo "The given IPv4 is valid." fi return 0 else if [[ -t 1 ]]; then echo "The given parameter is NOT a valid IPv4." fi return 1 fi else if [[ -t 1 ]]; then echo "The given parameter is NOT a valid IPv4." fi return 1 fi } export -f isipv4 # ------------------------------------------------------------------------------ # Determine if parameter is a valid IPv4 address # ------------------------------------------------------------------------------ isipv6 () { local ip="$1" local regex='^([0-9a-fA-F]{0,4}:){1,7}[0-9a-fA-F]{0,4}$' if [[ $ip =~ $regex ]]; then if [[ -t 1 ]]; then echo "The given IPv6 is valid." fi return 0 else if [[ -t 1 ]]; then echo "The given parameter is not a valid IPv6." fi return 1 fi } export -f isipv6 # ------------------------------------------------------------------------------ # Change locale to French # ------------------------------------------------------------------------------ setfr () { # Set fr locale definitions export LANG=fr_FR.UTF-8 export LC_MESSAGES=fr_FR.UTF-8 export LC_ALL=fr_FR.UTF-8 } export -f setfr # ------------------------------------------------------------------------------ # Change locale to C standard # ------------------------------------------------------------------------------ setc () { # Locale definitions export LANG=C export LC_MESSAGES=C export LC_ALL=C } export -f setc # ------------------------------------------------------------------------------ # Change locale to US (needed by Steam) # ------------------------------------------------------------------------------ setus () { # Locale definitions export LANG=en_US.UTF-8 export LC_MESSAGES=en_US.UTF-8 export LC_ALL=en_US.UTF-8 } export -f setus # ------------------------------------------------------------------------------ # Display weather of the given city (or default one) # ------------------------------------------------------------------------------ meteo () { cities=$@ [[ $# -eq 0 ]] && local cities=$DEFAULT_CITY for city in $cities; do curl https://wttr.in/$city done } export -f meteo # ------------------------------------------------------------------------------ # Clean a directory or a tree from temporary or backup files # ------------------------------------------------------------------------------ clean () { for opt in $@ ; do case $opt in "-r"|"--recurs") local recursive=1 ;; "-h"|"--help") echo "clean: erase backup files in the given directories." echo echo "Usage: clean [option] [directory1] [...[directoryX]]" echo echo "Options:" echo " -h, --help Display that help screen" echo " -r, --recurs Do a recursive cleaning" echo " -f, --force Do not ask for confirmation (use with care)" echo " -s, --shell Do nothing and display what will be executed" echo return 0 ;; "-s"|"--shell") local outshell=1 ;; "-f"|"--force") local force=1 ;; "-"*) echo "Invalid option, use \"clean --help\" to display usage." echo return 1 ;; *) local dirlist="$dirlist $opt" ;; esac done [[ ! $dirlist ]] && local dirlist=$(pwd) [[ ! $recursive ]] && local findopt="-maxdepth 1" [[ ! $force ]] && local rmopt="-i" unset recursive force for dir in $dirlist; do local dellist=$(find $dir $findopt -type f -name "*~" -o -name "#*#" \ -o -name "*.bak" -o -name ".~*#") for f in $dellist; do if [[ ! $outshell ]]; then rm $rmopt $f else echo "rm $rmopt $f" fi done done unset outshell dirlist dellist findopt rmopt } export -f clean # ------------------------------------------------------------------------------ # Login root via SSH on the given machine # ------------------------------------------------------------------------------ ssr () { for opt in $@ ; do case $opt in "-h"|"--help") echo "ssr: do a root user ssh login." echo echo "Usage: ssr " return 0 ;; esac done [[ ! $1 ]] && echo "Please specify the server you want to log in." && return 1 local srv=$1 && shift ssh -Y root@$srv $@ } export -f ssr # ------------------------------------------------------------------------------ # Look for a package within installed one # ------------------------------------------------------------------------------ dpkgs () { local count=0 for opt in $@ ; do case $opt in "-h"|"--help") echo "dpkgs: look for an installed package by it's name." echo echo "Usage: dpkgs " return 0 ;; "-"*) echo "Invalid option, use \"dpkgs --help\" to display usage." echo return 1 ;; *) local pkg=$1 && shift count=$(( $count + 1 )) [[ $count -gt 1 ]] && echo "*** Error: Please specify a package name, without space, eventually partial." && return 1 ;; esac done [[ $count -lt 1 ]] && echo "*** Error: Please specify a package name, without space, eventually partial." && return 1 [[ -x /usr/sbin/dpkg ]] && echo "*** Error: dpkg command seems unavialable." && return 2 dpkg -l | grep $pkg } export -f dpkgs # ------------------------------------------------------------------------------ # Search processes matching the given string # ------------------------------------------------------------------------------ ppg () { ps -edf | grep $@ | grep -v "grep $@" } export -f ppg # ------------------------------------------------------------------------------ # Create a directory then goes inside # ------------------------------------------------------------------------------ mcd () { if [[ ! $# -eq 1 ]] ; then echo "Create a directory then goes inside." echo "Usage: mcd " return 1 fi mkdir -pv $1 && cd $1 } export -f mcd # ------------------------------------------------------------------------------ # Get PID list of the given process name # ------------------------------------------------------------------------------ gpid () { [[ $# -eq 1 ]] && local single=1 for pid in $@; do local result=$(ps -A | grep $pid | awk '{print $1}') if [[ $single ]]; then [[ $result ]] && echo "$result" else [[ $result ]] && echo "$pid: $result" fi done [[ $result ]] || return 1 } export -f gpid # ------------------------------------------------------------------------------ # Remove host from know_host (name and IP) for the active user # ------------------------------------------------------------------------------ rmhost () { if [[ "$#" -lt 1 ]]; then echo "Error: incorrect number of parameters." echo "Usage: rmhost [hostname2|ip2 [...]]" return 1 fi while [[ $1 ]]; do local hst=$1 && shift isipv4 $hst > /dev/null local v4=$? isipv6 $hst > /dev/null local v6=$? if [[ $v4 -eq 0 || $v6 -eq 0 ]]; then local ip=$hst unset hst fi unset v4 v6 if [[ ! $ip && $hst ]]; then ip=$(host $hst | grep "has address" | awk '{print $NF}') [[ ! $? ]] && echo "*** rmhost(): Error extracting IP from hostname." && return 1 fi if [[ $hst ]]; then echo "Removing host $hst from ssh known_host..." ssh-keygen -R $hst > /dev/null fi if [[ $ip ]]; then echo "Removing IP $ip from ssh known_host..." ssh-keygen -R $ip > /dev/null fi unset hst ip done } export -f rmhost # ------------------------------------------------------------------------------ # Rename all files in current directory to replace spaces with _ # ------------------------------------------------------------------------------ rmspc () { local lst="" for opt in $@ ; do case $opt in "-h"|"--help") echo "rmspc: remove spaces from all filenames in current directories" echo echo "Usage: rmspc [option]" echo echo "Options:" echo " -h, --help Display that help screen" echo " -r, --recursive Treat subdirectories of the given directory" echo " -c, --subst-char Change the replacement character (default is underscore)" echo " -v, --verbose Display what is being done" echo " -s, --shell Do nothing and display commands that would be executed" echo return 0 ;; "-r"|"--recursive") local recurs=1 ;; "-c"?*|"--subst-char"?*) local substchar=$(echo "$opt" | cut -f 2- -d '=') ;; "-v"|"--verbose") local verb=1 ;; "-s"|"--shell") local shell=1 ;; *) echo "Invalid parameter, use \"rmspc --help\" to display options list" echo return 1 ;; esac done [[ ! $substchar ]] && substchar="_" [[ $verb ]] && local mvopt="-v" for f in *; do [[ $recurs ]] && [[ -d "$f" ]] && ( [[ $verb ]] && echo "-- Entering directory $(pwd)/$f ..." local lastdir=$f pushd "$f" > /dev/null rmspc $@ popd > /dev/null [[ $verb ]] && echo "-- Leaving directory $(pwd)/$lastdir" unset lastdir ) if [[ $(echo $f | grep " ") ]]; then local newf="${f// /${substchar}}" local command="mv $mvopt \"$f\" \"$newf\"" if [[ $shell ]]; then echo $command else $command fi fi done unset lst substchar verb shell newf command mvopt } export -f rmspc # ------------------------------------------------------------------------------ # Smartly uncompress archives (zip only) # ------------------------------------------------------------------------------ utaz() { for opt in $@ ; do case $opt in "-h"|"--help") echo "utaz: uncompress all the given files and/or the ones found in the given" echo " directories creating an host directory where needed." echo echo "Usage: utaz [option] [directorie(s)|file(s)]" echo echo "Options:" echo " -h, --help Display that help screen" echo " -d, --delete If decompression succeeded, delete the source file" echo " -c, --create-dir Always create a host directory" echo " -n, --no-dir Never create a host directory" echo return 0 ;; "-d"|"--delete") local willrm=1 ;; "-c"|"--create-dir") local createdir=1 ;; "-n"|"--no-dir") local nodir=1 ;; "-"*) echo "Invalid option, use \"utaz --help\" to display options list" echo return 1 ;; *) # The ${opt%/} writing is to remove trailing / if any local LIST="$LIST ${opt%/}" ;; esac done [[ $createdir && $nodir ]] && echo "*** Error: --create-dir and --no-dir options are mutually exclusive." [[ ! $LIST ]] && local LIST="." for zitem in $LIST; do [[ $(ls $zitem/*.zip 2> /dev/null | wc -l) -eq 0 ]] && echo "$zitem contains no supported archive file, skipping." && continue for f in $zitem/*.zip; do echo -n "Processing archive $zitem/$f... " local dir=${f::-4} mkdir -p $dir [[ $? -gt 0 ]] && echo "[ filesystem can't create directories, exit ]" && return 1 unzip -o $f -d $dir > /dev/null 2>&1 case $? in 0) [[ $willrm ]] && rm -f $f && echo -n "Deleted ! " ;; 1) echo "No deletion on warnings " ;; *) echo "[ zip file corrupted, failed ]" rm -rf $dir > /dev/null 2>&1 continue ;; esac if [[ $createdir ]]; then echo -n "[ subdir created, " elif [[ $nodir ]]; then mv ./$dir/* ./ && rmdir $dir echo -n "[ No subdir, " else subdirs=$(find $dir -maxdepth 1 | wc -l) if [[ $subdirs -eq 2 ]]; then mv ./$dir/* ./ && rmdir $dir echo -n "[ No subdir, " else echo -n "[ subdir created, " fi fi echo " OK ]" done done } export -f utaz # ------------------------------------------------------------------------------ # Compress directories or files into one or more archive # ------------------------------------------------------------------------------ taz () { _doxz() { command -v xz >/dev/null 2>&1 || { echo -e >&2 "\t*** The program 'xz' is not installed, aborting." return 127 } [[ $4 ]] && local verb='-v' # Display a warning for this format echo -e "\t! Warning: xz format is not suited for long term archiving." echo -e "\t See https://www.nongnu.org/lzip/xz_inadequate.html for details." # Compresse to xz (lzma2) - Deprecated xz $verb --compress --keep -$3 -T $2 $1 return $? } _dolz() { local procopt="--threads $2" local command=plzip command -v plzip >/dev/null 2>&1 || { command -v lzip >/dev/null 2>&1 || { echo -e >&2 "\t*** Program 'plzip' or 'lzip' are not installed, aborting." return 127 } local command=lzip local procopt="" [[ $2 -gt 1 ]] && echo -e "\t! Warning: lzip doesn't support multithreading, falling back to 1 thread." && echo -e "\t* Consitder installing plzip to obtain multithreading abilities." } [[ $4 ]] && local verb="-vv" # Compresse au format lzip (lzma) $command $verb $procopt --keep -$3 $1 return $? } _dogz() { local procopt="--processes $2" local command=pigz command -v pigz >/dev/null 2>&1 || { command -v gzip >/dev/null 2>&1 || { echo -e >&2 "\t*** Programs 'pigz' or 'gzip' are not installed, aborting." return 127 } local command="gzip --compress" local procopt="" [[ $2 -gt 1 ]] && echo -e "\t! Warning: gzip doesn't support multithreading, falling back to 1 thread." && echo -e "\t* Consitder installing pigz to obtain multithreading abilities." } [[ $4 ]] && local verb="--verbose" # Compresse au format bz2 $command $verb $procopt --keep -$3 $1 return $? } _dobz2() { local procopt="-p$2" local command=pbzip2 command -v pbzip2 >/dev/null 2>&1 || { command -v bzip2 >/dev/null 2>&1 || { echo -e >&2 "\t*** The program 'pbzip2' or 'bzip2' are not installed, aborting." return 127 } local command=bzip2 local procopt="" [[ $2 -gt 1 ]] && echo -e "\t! Warning: bzip2 doesn't support multithreading, falling back to 1 thread." && echo -e "\t* Consitder installing pbzip2 to obtain multithreading abilities." } [[ $4 ]] && local verb="-v" # Compresse au format bz2 $command $verb --compress $procopt --keep -$3 $1 return $? } _dolzo() { command -v lzop >/dev/null 2>&1 || { echo -e >&2 "\t*** The program 'lzop' is not installed, aborting." return 127 } [[ $4 ]] && local verb='-v' [[ $2 -gt 1 ]] && echo -e "\t! Warning: lzop doesn't support multithreading, falling back to 1 thread." # Compresse au format lzo lzop --keep -$3 $1 return $? } for opt in $@ ; do case $opt in "-h"|"--help") echo "taz: archive all files of a directory." echo echo "Usage: taz [option] [--parallel=] [--format=] [directory1 ... directoryN]" echo echo "Options:" echo " -h, --help Display that help screen" echo " -d, --delete Delete source file or directory after success" echo " -f, --format Chose archive format in the given list. If several format are" echo " given, the smalest is kept" echo " -p, --parallel Number of threads to use (if allowed by underlying utility)" echo " -v, --verbose Display progress where possible" echo " -1, .., -9 Compression level to use [1=fast/big, 9=slow/small]" echo echo "Supported archive format:" echo " Param.| programs | Algo. | Description" echo " ------+---------------+-------+----------------------------------------" echo " lz | plzip, lzip | lzma | Safe efficient default format" echo " xz | xz | lzma2 | Unsafe, not for long term" echo " bz2 | pbzip2, bzip2 | bzip2 | Historical but less efficient than lz" echo " gz | pigz, gzip | lz77 | Historical, safe, fast" echo " lzo | lzop | lzo | Very fast but no multithread" echo " tar | tar | tar | No compression" echo return 0 ;; "-d"|"--delete") local willrm=1 ;; "-f"?*|"--format"?*) local compform=$(echo "$opt" | cut -f 2- -d '=') ;; "-p"?*|"--parallel"?*) local nproc=$(echo "$opt" | cut -f 2- -d '=') ;; "-v"|"--verbose") local verbose=1 ;; "-"[1..9]) local complevel=${opt:1:1} ;; "-"*) echo "Invalid option, use taz --help to display options list" echo return 1 ;; *) local LIST="$LIST ${opt%/}" ;; esac done [[ ! $compform ]] && compform=lz # safe and efficient (unless data are already compressed) [[ ! $nproc ]] && nproc=1 [[ ! $complevel ]] && complevel=6 for item in $LIST; do local donetar=0 echo "--- Processing $item..." if [[ -d $item ]]; then echo -ne "\t* Creating $item.tar... " tar -cf $item{.tar,} if [[ ! $? -eq 0 ]]; then echo "[ failed, skipping ]" continue fi local donetar=1 echo "[ OK ]" fi local fname=$item [[ $donetar -gt 0 ]] && fname=$item.tar # Skip compression part if tar is asked if [[ $compform != "tar" ]]; then echo -e "\t* Compressing archive..." _do$compform $fname $nproc $complevel $verbose [[ ! $? -eq 0 ]] && case $? in 127) echo -e "\t*** Compression program unavailable, aborting." return 127 ;; *) echo -e "\t*** Compression program returned an error, not deleting anything if asked, skipping to next item." continue ;; esac [[ $donetar -gt 0 ]] && rm $fname fi if [[ $willrm ]]; then echo -en "\t* Deleting original source as asked... " rm -r $item && echo '[ OK ]' || echo '[ failed ]' fi echo "--- Done" done } export -f taz # ------------------------------------------------------------------------------ # Display system general information # ------------------------------------------------------------------------------ showinfo() { echo -e "\n" command -v figlet >/dev/null 2>&1 && figlet -k $(hostname) echo "" command -v neofetch >/dev/null 2>&1 && neofetch } export -f showinfo # ------------------------------------------------------------------------------ # Display list of commands and general informations # ------------------------------------------------------------------------------ help () { cat <