#!/bin/sh # # pfetch - Simple POSIX sh fetch script. log() { # The 'log()' function handles the printing of information. # In 'pfetch' (and 'neofetch'!) the printing of the ascii art and info # happen independently of each other. # # The size of the ascii art is stored and the ascii is printed first. # Once the ascii is printed, the cursor is located right below the art # (See marker $[1]). # # Using the stored ascii size, the cursor is then moved to marker $[2]. # This is simply a cursor up escape sequence using the "height" of the # ascii art. # # 'log()' then moves the cursor to the right the "width" of the ascii art # with an additional amount of padding to add a gap between the art and # the information (See marker $[3]). # # When 'log()' has executed, the cursor is then located at marker $[4]. # When 'log()' is run a second time, the next line of information is # printed, moving the cursor to marker $[5]. # # Markers $[4] and $[5] repeat all the way down through the ascii art # until there is no more information left to print. # # Every time 'log()' is called the script keeps track of how many lines # were printed. When printing is complete the cursor is then manually # placed below the information and the art according to the "heights" # of both. # # The math is simple: move cursor down $((ascii_height - info_height)). # If the aim is to move the cursor from marker $[5] to marker $[6], # plus the ascii height is 8 while the info height is 2 it'd be a move # of 6 lines downwards. # # However, if the information printed is "taller" (takes up more lines) # than the ascii art, the cursor isn't moved at all! # # Once the cursor is at marker $[6], the script exits. This is the gist # of how this "dynamic" printing and layout works. # # This method allows ascii art to be stored without markers for info # and it allows for easy swapping of info order and amount. # # $[2] ___ $[3] goldie@KISS # $[4](.· | $[5] os KISS Linux # (<> | # / __ \ # ( / \ /| # _/\ __)/_) # \/-____\/ # $[1] # # $[6] /home/goldie $ # End here if no data was found. [ "$2" ] || return # Store the value of '$1' as we reset the argument list below. name=$1 # Use 'set --' as a means of stripping all leading and trailing # white-space from the info string. This also normalizes all # whitespace inside of the string. # # Disable the shellcheck warning for word-splitting # as it's safe and intended ('set -f' disables globbing). # shellcheck disable=2046,2086 { set -f set +f -- $2 info=$* } # Move the cursor to the right, the width of the ascii art with an # additional gap for text spacing. printf '[%sC' "${ascii_width--1}" # Print the info name and color the text. printf '[3%s;1m%s' "${PF_COL1-4}" "$name" # Print the info name and info data separator. printf '%s' "$PF_SEP" # Move the cursor backward the length of the *current* info name and # then move it forwards the length of the *longest* info name. This # aligns each info data line. printf '[%sD[%sC' "${#name}" "${PF_ALIGN-$info_length}" # Print the info data, color it and strip all leading whitespace # from the string. printf '[3%sm%s\n' "${PF_COL2-7}" "$info" # Keep track of the number of times 'log()' has been run. : $((info_height+=1)) } get_title() { # Username is retrieved by first checking '$USER' with a fallback # to the 'whoami' command. user=${USER:-$(whoami)} # Hostname is retrieved by first checking '$HOSTNAME' with a fallback # to the 'hostname' command. # # Disable the warning about '$HOSTNAME' being undefined in POSIX sh as # the intention for using it is allowing the user to overwrite the # value on invocation. # shellcheck disable=SC2039 hostname=${HOSTNAME:-${hostname:-$(hostname)}} log "[3${PF_COL3:-1}m${user}${c7}@[3${PF_COL3:-1}m${hostname}" " " >&6 } get_os() { # This function is called twice, once to detect the distribution name # for the purposes of picking an ascii art early and secondly to display # the distribution name in the info output (if enabled). # # On first run, this function displays _nothing_, only on the second # invocation is 'log()' called. [ "$distro" ] && { log os "$distro" >&6 return } case $os in Linux*) # Some Linux disttributions (which are based on others) # fail to identify as they **do not** change the upstream # distributions identification packages or files. # # It is senseless to add a special case in the code for # each and every distribution (which _is_ technically no # different from what it is based on) as they're either too # lazy to modify upstream's identification files or they # don't have the know-how (or means) to ship their own # lsb-release package. # # This causes users to think there's a bug in system detection # tools like neofetch or pfetch when they technically *do* # function correctly. # # Exceptions are made for distributions which are independent, # not based on another distribution or follow different # standards. # # This applies only to distributions which follow the standard # by shipping unmodified identification files and packages # from their respective upstreams. if command -v lsb_release; then distro=$(lsb_release -sd) else # Disable warning about shellcheck not being able # to read '/etc/os-release'. This is fine. # shellcheck source=/dev/null . /etc/os-release && distro=$PRETTY_NAME fi # Special cases for (independent) distributions which # don't follow any os-release/lsb standards whatsoever. command -v crux && distro=$(crux) command -v guix && distro='Guix System' ;; Darwin*) # Parse the SystemVersion.plist file to grab the macOS # version. The file is in the following format: # # ProductVersion # 10.14.6 # # 'IFS' is set to '<>' to enable splitting between the # keys and a second 'read' is used to operate on the # next line directly after a match. # # '_' is used to nullify a field. '_ _ line _' basically # says "populate $line with the third field's contents". while IFS='<>' read -r _ _ line _; do case $line in # Match 'ProductVersion' and read the next line # directly as it contains the key's value. ProductVersion) IFS='<>' read -r _ _ mac_version _ break ;; esac done < /System/Library/CoreServices/SystemVersion.plist # Use the ProductVersion to determine which macOS/OS X codename # the system has. As far as I'm aware there's no "dynamic" way # of grabbing this information. case $mac_version in 10.4*) distro='Mac OS X Tiger' ;; 10.5*) distro='Mac OS X Leopard' ;; 10.6*) distro='Mac OS X Snow Leopard' ;; 10.7*) distro='Mac OS X Lion' ;; 10.8*) distro='OS X Mountain Lion' ;; 10.9*) distro='OS X Mavericks' ;; 10.10*) distro='OS X Yosemite' ;; 10.11*) distro='OS X El Capitan' ;; 10.12*) distro='macOS Sierra' ;; 10.13*) distro='macOS High Sierra' ;; 10.14*) distro='macOS Mojave' ;; 10.15*) distro='macOS Catalina' ;; *) distro='macOS' ;; esac distro="$distro $mac_version" ;; Haiku) # Haiku uses 'uname -v' for version information # instead of 'uname -r'. distro="Haiku $(uname -v)" ;; *) # Catch all to ensure '$distro' is never blank. # This also handles the BSDs. distro="$os $kernel" ;; esac } get_kernel() { case $os in # Don't print kernel output on some systems as the # OS name includes it. *BSD*|Haiku) ;; *) # '$kernel' is the cached output of 'uname -r'. log kernel "$kernel" >&6 ;; esac } get_host() { case $os in Linux*) # Despite what these files are called, version doesn't # always contain the version nor does name always contain # the name. read -r name < /sys/devices/virtual/dmi/id/product_name read -r version < /sys/devices/virtual/dmi/id/product_version read -r model < /sys/firmware/devicetree/base/model host="$name $version $model" ;; Darwin*|FreeBSD*) host=$(sysctl -n hw.model) ;; NetBSD*) host=$(sysctl -n machdep.dmi.system-vendor \ machdep.dmi.system-product) ;; *BSD*) host=$(sysctl -n hw.vendor hw.product) ;; esac # Turn the host string into an argument list so we can iterate # over it and remove OEM strings and other information which # shouldn't be displayed. # # Disable the shellcheck warning for word-splitting # as it's safe and intended ('set -f' disables globbing). # shellcheck disable=2046,2086 { set -f set +f -- $host host= } # Iterate over the host string word by word as a means of stripping # unwanted and OEM information from the string as a whole. # # This could have been implemented using a long 'sed' command with # a list of word replacements, however I want to show that something # like this is possible in pure sh. # # This string reconstruction is needed as some OEMs either leave the # identification information as "To be filled by OEM", "Default", # "undefined" etc and we shouldn't print this to the screen. for word; do # This works by reconstructing the string by excluding words # found in the "blacklist" below. Only non-matches are appended # to the final host string. case $word in To | [Bb]e | [Ff]illed | by | O.E.M. | OEM |\ Not | Applicable | Specified | System | Product | Name |\ Version | Undefined | Default | string | INVALID | � ) continue ;; esac host="$host$word " done # '$arch' is the cached output from 'uname -m'. log host "${host:-$arch}" >&6 } get_uptime() { # Uptime works by retrieving the data in total seconds and then # converting that data into days, hours and minutes using simple # math. case $os in Linux*) IFS=. read -r s _ < /proc/uptime ;; Darwin*|*BSD*) s=$(sysctl -n kern.boottime) # Extract the uptime in seconds from the following output: # [...] { sec = 1271934886, usec = 667779 } Thu Apr 22 12:14:46 2010 s=${s#*=} s=${s%,*} # The uptime format from 'sysctl' needs to be subtracted from # the current time in seconds. s=$(($(date +%s) - s)) ;; Haiku) # The boot time is returned in microseconds, convert it to # regular seconds. s=$(($(system_time) / 1000000)) ;; esac # Convert the uptime from seconds into days, hours and minutes. d=$((s / 60 / 60 / 24)) h=$((s / 60 / 60 % 24)) m=$((s / 60 % 60)) # Only append days, hours and minutes if they're non-zero. [ "$d" = 0 ] || uptime="${uptime}${d}d " [ "$h" = 0 ] || uptime="${uptime}${h}h " [ "$m" = 0 ] || uptime="${uptime}${m}m " log uptime "${uptime:-0m}" >&6 } get_pkgs() { # This works by first checking for which package managers are # installed and finally by printing each package manager's # package list with each package one per line. # # The output from this is then piped to 'wc -l' to count each # line, giving us the total package count of whatever package # managers are installed. # # Backticks are *required* here as '/bin/sh' on macOS is # 'bash 3.2' and it can't handle the following: # # var=$( # code here # ) # # shellcheck disable=2006 packages=` case $os in Linux*) # Commands which print packages one per line. command -v kiss && kiss l command -v bonsai && bonsai list command -v pacman-key && pacman -Qq command -v dpkg && dpkg-query -f '.\n' -W command -v rpm && rpm -qa command -v xbps-query && xbps-query -l command -v apk && apk info # Directories containing packages. command -v brew && printf '%s\n' "$(brew --cellar)/"* command -v emerge && printf '%s\n' /var/db/pkg/*/*/ command -v pkgtool && printf '%s\n' /var/log/packages/* # GUIX requires two commands. command -v guix && { guix package -p /run/current-system/profile -I guix package -I } # NIX requires two commands. command -v nix-store && { nix-store -q --requisites /run/current-system/sw nix-store -q --requisites ~.nix-profile } ;; Darwin*) # Commands which print packages one per line. command -v pkgin && pkgin list command -v port && port installed # Directories containing packages. command -v brew && printf '%s\n' /usr/local/Cellar/* ;; FreeBSD*) pkg info ;; OpenBSD*) printf '%s\n' /var/db/pkg/*/ ;; NetBSD*) pkg_info ;; Haiku) printf '%s\n' /boot/system/package-links/* ;; esac | wc -l ` log pkgs "${packages:-?}" >&6 } get_memory() { case $os in # Used memory is calculated using the following "formula" (Linux): # MemUsed = MemTotal + Shmem - MemFree - Buffers - Cached - SReclaimable # Source: https://github.com/KittyKatt/screenFetch/issues/386 Linux*) # Parse the '/proc/meminfo' file splitting on ':' and 'k'. # The format of the file is 'key: 000kB' and an additional # split is used on 'k' to filter out 'kB'. while IFS=:k read -r key val _; do case $key in MemTotal) mem_used=$((mem_used + val)) mem_full=$val ;; Shmem) mem_used=$((mem_used + val)) ;; MemFree|Buffers|Cached|SReclaimable) mem_used=$((mem_used - val)) ;; esac done < /proc/meminfo mem_used=$((mem_used / 1024)) mem_full=$((mem_full / 1024)) ;; # Used memory is calculated using the following "formula" (MacOS): # (wired + active + occupied) * 4 / 1024 Darwin*) mem_full=$(($(sysctl -n hw.memsize) / 1024 / 1024)) # Parse the 'vmstat' file splitting on ':' and '.'. # The format of the file is 'key: 000.' and an additional # split is used on '.' to filter it out. while IFS=:. read -r key val; do case $key in *wired*|*active*|*occupied*) mem_used=$((mem_used + ${val:-0})) ;; esac # Using '<<-EOF' is the only way to loop over a command's # output without the use of a pipe ('|'). # This ensures that any variables defined in the while loop # are still accessible in the script. done <<-EOF $(vm_stat) EOF mem_used=$((mem_used * 4 / 1024)) ;; OpenBSD*) mem_full=$(($(sysctl -n hw.physmem) / 1024 / 1024)) # This is a really simpler parser for 'vmstat' which grabs # the used memory amount in a lazy way. 'vmstat' prints 3 # lines of output with the needed value being stored in the # final line. # # This loop simply grabs the 3rd element of each line until # the EOF is reached. Each line overwrites the value of the # previous one so we're left with what we wanted. This isn't # slow as only 3 lines are parsed. while read -r _ _ line _; do mem_used=${line%%M} # Using '<<-EOF' is the only way to loop over a command's # output without the use of a pipe ('|'). # This ensures that any variables defined in the while loop # are still accessible in the script. done <<-EOF $(vmstat) EOF ;; # Used memory is calculated using the following "formula" (FreeBSD): # (inactive_count + free_count + cache_count) * page_size / 1024 FreeBSD*) mem_full=$(($(sysctl -n hw.physmem) / 1024 / 1024)) # Use 'set --' to store the output of the command in the # argument list. POSIX sh has no arrays but this is close enough. # # Disable the shellcheck warning for word-splitting # as it's safe and intended ('set -f' disables globbing). # shellcheck disable=2046 { set -f set +f -- $(sysctl -n hw.pagesize \ vm.stats.vm.v_inactive_count \ vm.stats.vm.v_free_count \ vm.stats.vm.v_cache_count) } # Calculate the amount of used memory. # $1: hw.pagesize # $2: vm.stats.vm.v_inactive_count # $3: vm.stats.vm.v_free_count # $4: vm.stats.vm.v_cache_count mem_used=$((($2 + $3 + $4) * $1 / 1024 / 1024)) ;; NetBSD*) mem_full=$(($(sysctl -n hw.physmem64) / 1024 / 1024)) # NetBSD implements a lot of the Linux '/proc' filesystem, # this uses the same parser as the Linux memory detection. while IFS=:k read -r key val _; do case $key in MemFree) mem_free=$((val / 1024)) break ;; esac done < /proc/meminfo mem_used=$((mem_full - mem_free)) ;; Haiku) # Read the first line of 'sysinfo -mem' splitting on # '(', ' ', and ')'. The needed information is then # stored in the 5th and 7th elements. Using '_' "consumes" # an element allowing us to proceed to the next one. # # The parsed format is as follows: # 3501142016 bytes free (used/max 792645632 / 4293787648) IFS='( )' read -r _ _ _ _ mem_used _ mem_full <<-EOF $(sysinfo -mem) EOF mem_used=$((mem_used / 1024 / 1024)) mem_full=$((mem_full / 1024 / 1024)) ;; esac log memory "${mem_used:-?}M / ${mem_full:-?}M" >&6 } get_term() { # Workaround for macOS systems that don't support the # "algorithm" of obtaining the terminal program name. # # This also doubles as a means of allowing the user to # set whatever value they like here through the # '$TERM_PROGRAM' environment variable. case $TERM_PROGRAM in iTerm.app) term=iTerm2 ;; Terminal.app) term='Apple Terminal' ;; Hyper) term=HyperTerm ;; *) term=${TERM_PROGRAM%%.app} ;; esac # Special case for TosWin2 (FreeMiNT) which doesn't # support the "algorithm" of obtaining the terminal # program name. [ "$TERM" = tw52 ] || [ "$TERM" = tw100 ] && term=TosWin2 # Special case for when 'pfetch' is run over SSH. [ "$SSH_CONNECTION" ] && term=$SSH_TTY # This surprisingly reliable method of detecting the current # terminal emulator is kinda neat. # # It works by looping through each parent of each process # starting with '$PPID' (the parent process ID) until we # find a match or hit PID 1 (init). # # On each iteration the name of the current parent process # is checked against a list of good values and bad values. # If no match is found we check the parent of the parent # and so on. # # Using this method *no* terminal emulator names are # hardcoded and the list remains small and general. In short # it's basically a list of what *isn't* a terminal emulator # and a list of places we should *stop*. while [ -z "$term" ]; do # This block is OS-specific and handles the fetching of # the parent process (of the parent) and the fetching of # said process' name. case $os in Linux*) # On Linux some implementation of 'ps' aren't POSIX # compliant, thankfully Linux provides this information # though the '/proc' filesystem. # # This loops line by line over the '/proc/PID/status' # file splitting at ':' and '', we then look for # the key containing 'PPid' and grab the value. while IFS=': ' read -r key val; do case $key in PPid) ppid=$val break ;; esac done < "/proc/${ppid:-$PPID}/status" # Get the name of the parent process. read -r name < "/proc/$ppid/comm" ;; Windows*) # I need some assistance to add Windows support # as the 'ps' command used in MINGW, MSYS and CYGWIN # isn't POSIX compliant(?). return ;; *) # POSIX compliant 'ps' makes this really easy, # just two simple commands to grab the parent # process ID and the ID's name. ppid=$(ps -p "${ppid:-$PPID}" -o ppid=) name=$(ps -p "$ppid" -o comm=) ;; esac # Check the parent process name against a list of good and bad # values. On a bad value we either keep iterating up the parent # process list or we stop altogether (PID 1 for example). case $name in # If the parent process name matches the user's shell (or # anything that looks like a shell), do another iteration. # # This also includes 'screen' and anything that looks like # 'su' or 'sudo'. ${SHELL##*/} | *sh | screen | su* ) ;; # If the parent process name matches 'login', 'init' or # '*Login*' we're most likely in the TTY and not a graphical # session. In this case 'term' is set to the current TTY and # we end here. login* | *Login* | init) term=$(tty) ;; # If the parent process name matches anything in this list # we can no longer continue. We've either hit PID 1 or a parent # which *won't* lead to the terminal emulator's PID. ruby | systemd | python* | 1 | sshd* | tmux* |\ USER*PID* | kdeinit* | launchd* | '' ) break ;; # If none of the above have matched we've reached the terminal # emulator's PID and we can end here. *) term=${name##*/} ;; esac done [ "$term" ] && log term "$term" >&6 } get_shell() { log shell "${SHELL##*/}" >&6 } get_ascii() { # This is a simple function to read the contents of # an ascii file from 'stdin'. It allows for the use # of '<<-EOF' to prevent the break in indentation in # this source code. # # This function also sets the text colors according # to the ascii color. read_ascii() { # 'PF_COL1': Set the info name color according to ascii color. # 'PF_COL3': Set the title color to some other color. ¯\_(ツ)_/¯ PF_COL1=${PF_COL1:-${1:-7}} PF_COL3=${PF_COL3:-$((${1:-7}%8+1))} # POSIX sh has no 'var+=' so 'var=${var}append' is used. What's # interesting is that 'var+=' _is_ supported inside '$(())' # (arithmetic) though there's no support for 'var++/var--'. # # There is also no $'\n' to add a "literal"(?) newline to the # string. The simplest workaround being to break the line inside # the string (though this has the caveat of breaking indentation). while IFS= read -r line; do ascii="$ascii$line " done } # This checks for ascii art in the following order: # '$1': Argument given to 'get_ascii()' directly. # '$PF_ASCII': Environment variable set by user. # '$distro': The detected distribution name. # '$os': The name of the operating system/kernel. # # NOTE: Each ascii art below is indented using tabs, this # allows indentation to continue naturally despite # the use of '<<-EOF'. case ${1:-${PF_ASCII:-${distro:-$os}}} in [Aa]lpine*) read_ascii 4 <<-EOF ${c4} /\\ /\\ /${c7}/ ${c4}\\ \\ /${c7}/ ${c4}\\ \\ /${c7}// ${c4}\\ \\ ${c7}// ${c4}\\ \\ \\ EOF ;; [Aa]rch*) read_ascii 4 <<-EOF ${c6} /\\ /^^\\ /\\ \\ /${c7} __ \\ / ( ) \\ / __| |__\\\\ /// \\\\\\ EOF ;; [Aa]rco*) read_ascii 4 <<-EOF ${c4} /\\ / \\ / /\\ \\ / / \\ \\ / / \\ \\ / / _____\\ \\ /_/ \`----.\\_\\ EOF ;; [Aa]rtix*) read_ascii 6 <<-EOF ${c4} /\\ / \\ /\`'.,\\ / ', / ,\`\\ / ,.'\`. \\ /.,'\` \`'.\\ EOF ;; [Cc]ent[Oo][Ss]*) read_ascii 5 <<-EOF ${c2} ____${c3}^${c5}____ ${c2} |\\ ${c3}|${c5} /| ${c2} | \\ ${c3}|${c5} / | ${c5}<---- ${c4}----> ${c4} | / ${c2}|${c3} \\ | ${c4} |/__${c2}|${c3}__\\| ${c2} v EOF ;; [Dd]ebian*) read_ascii 1 <<-EOF ${c1} _____ / __ \\ | / | | \\___- -_ --_ EOF ;; [Ee]lementary*) read_ascii <<-EOF ${c7} _______ / ____ \\ / | / /\\ |__\\ / / | \\ /__/ / \\_______/ EOF ;; [Ff]edora*) read_ascii 4 <<-EOF ${c7} _____ / __)${c4}\\${c7} | / ${c4}\\ \\${c7} ${c4}__${c7}_| |_${c4}_/ /${c7} ${c4}/ ${c7}(_ _)${c4}_/${c7} ${c4}/ /${c7} | | ${c4}\\ \\${c7}__/ | ${c4}\\${c7}(_____/ EOF ;; [Ff]ree[Bb][Ss][Dd]*) read_ascii 1 <<-EOF ${c1} /\\ _____ /\\ \\_) (_/ / \\ | | | | \ / --_____-- EOF ;; [Gg]entoo*) read_ascii 5 <<-EOF ${c5} _-----_ ( \\ \\ 0 \\ ${c7} \\ ) / _/ ( _- \\____- EOF ;; [Gg]uix[Ss][Dd]*|guix*) read_ascii 3 <<-EOF ${c3}|.__ __.| |__ \\ / __| \\ \\ / / \\ \\ / / \\ \\ / / \\ \\/ / \\__/ EOF ;; [Hh]aiku*) read_ascii 3 <<-EOF ${c3} ,^, / \\ *--_ ; ; _--* \\ '" "' / '. .' .-'" "'-. '-.__. .__.-' |_| EOF ;; [Hh]yperbola*) read_ascii <<-EOF ${c7} |\`__.\`/ \____/ .--. / \\ / ___ \\ / .\` \`.\\ /.\` \`.\\ EOF ;; [Ll]inux*[Ll]ite*) read_ascii 3 <<-EOF ${c3} /\\ / \\ / ${c7}/ ${c3}/ > ${c7}/ ${c3}/ \\ ${c7}\\ ${c3}\\ \\_${c7}\\${c3}_\\ ${c7} \\ EOF ;; [Ll]inux*[Mm]int*|[Mm]int) read_ascii 2 <<-EOF ${c2} ___________ |_ \\ | ${c7}| _____ ${c2}| | ${c7}| | | | ${c2}| | ${c7}| | | | ${c2}| | ${c7}\\__${c7}___/ ${c2}| \\_________/ EOF ;; [Ll]inux*) read_ascii 4 <<-EOF ${c4} ___ (${c7}.· ${c4}| (${c5}<> ${c4}| / ${c7}__ ${c4}\\ ( ${c7}/ \\ ${c4}/| ${c5}_${c4}/\\ ${c7}__)${c4}/${c5}_${c4}) ${c5}\/${c4}-____${c5}\/ EOF ;; [Mm]ac[Oo][Ss]*|[Dd]arwin*) read_ascii 1 <<-EOF ${c1} .:' _ :'_ ${c2} .'\`_\`-'_\`\`. :________.-' ${c3}:_______: ${c4} :_______\`-; ${c5} \`._.-._.' EOF ;; [Mm]ageia*) read_ascii 2 <<-EOF ${c6} * * ** ${c7} /\\__/\\ / \\ \\ / \\____/ EOF ;; [Mm]anjaro*) read_ascii 2 <<-EOF ${c2}||||||||| |||| ||||||||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| |||| EOF ;; [Mm][Xx]*) read_ascii <<-EOF ${c7} \\\\ / \\\\/ \\\\ /\\/ \\\\ / \\ /\\ / \\/ \\ /__________\\ EOF ;; [Nn]et[Bb][Ss][Dd]*) read_ascii 3 <<-EOF ${c7}\\\\${c3}\`-______,----__ ${c7} \\\\ ${c3}__,---\`_ ${c7} \\\\ ${c3}\`.____ ${c7} \\\\${c3}-______,----\`- ${c7} \\\\ \\\\ \\\\ EOF ;; [Nn]ix[Oo][Ss]*) read_ascii 4 <<-EOF ${c4} \\\\ \\\\ // ==\\\\__\\\\/ // // \\\\// ==// //== //\\\\___// // /\\\\ \\\\== // \\\\ \\\\ EOF ;; [Oo]pen[Bb][Ss][Dd]*) read_ascii 3 <<-EOF ${c3} _____ \\- -/ \\_/ \\ | ${c7}O O${c3} | |_ < ) 3 ) / \\ / /-_____-\\ EOF ;; openSUSE*|open*SUSE*|SUSE*|suse*) read_ascii 2 <<-EOF ${c2} _______ __| __ \\ / .\\ \\ \\__/ | _______| \\_______ __________/ EOF ;; [Pp]arabola*) read_ascii 5 <<-EOF ${c5} __ __ __ _ .\`_//_//_/ / \`. / .\` / .\` /.\` /\` EOF ;; [Pp]op!_[Oo][Ss]*) read_ascii 6 <<-EOF ${c6}______ \\ _ \\ __ \\ \\ \\ \\ / / \\ \\_\\ \\ / / \\ ___\\ /_/ \\ \\ _ __\\_\\__(_)_ (___________) EOF ;; [Pp]ure[Oo][Ss]*) read_ascii <<-EOF ${c7} _____________ | _________ | | | | | | | | | | |_________| | |_____________| EOF ;; [Ss]lackware*) read_ascii 4 <<-EOF ${c4} ________ / ______| | |______ \\______ \\ ______| | | |________/ |____________ EOF ;; [Vv]oid*) read_ascii 2 <<-EOF ${c2} _______ _ \\______ - | \\ ___ \\ | | | / \ | | | | \___/ | | | \\______ \\_| -_______\\ EOF ;; *) # On no match of a distribution ascii art, this function calls # itself again, this time to look for a more generic OS related # ascii art (KISS Linux -> Linux). [ "$1" ] || { get_ascii "$os" return } printf 'error: %s is not currently supported.\n' "$os" >&6 printf 'error: Open an issue for support to be added.\n' >&6 exit 1 ;; esac # Store the "width" (longest line) and "height" (number of lines) # of the ascii art for positioning. This script prints to the screen # *almost* like a TUI does. It uses escape sequences to allow dynamic # printing of the information through user configuration. # # Iterate over each line of the ascii art to retrieve the above # information. The 'sed' is used to strip 'm' color codes from # the ascii art so they don't affect the width variable. while read -r line; do : $((ascii_height+=1)) ascii_width=$((${#line} > ascii_width ? ${#line} : ascii_width)) # Using '<<-EOF' is the only way to loop over a command's # output without the use of a pipe ('|'). # This ensures that any variables defined in the while loop # are still accessible in the script. done <<-EOF $(printf %s "$ascii" | sed 's/\[3.m//g') EOF # Add a gap between the ascii art and the information. : $((ascii_width+=4)) # Print the ascii art and position the cursor back where we # started prior to printing it. # '[?7l': Disable line-wrapping. # '[?25l': Hide the cursor. # '[1m': Print the ascii in bold. # '[m': Clear bold. # '[%sA': Move the cursor up '$ascii_height' amount of lines. printf '[?7l[?25l%s[%sA' "$ascii" "$ascii_height" >&6 } main() { # Leave the terminal how we found it on exit or Ctrl+C. # '[?7h': Enable line-wrapping. # '[?25h': Un-hide the cursor. trap 'printf [?7h[?25h >&6' EXIT # Hide 'stderr' unless the first argument is '-v'. This saves # polluting the script with '2>/dev/null'. [ "$1" = -v ] || exec 2>/dev/null # Hide 'stdout' and selectively print to it using '>&6'. # This gives full control over what it displayed on the screen. exec 6>&1 >/dev/null # Generic color list. # Disable warning about unused variables. # shellcheck disable=2034 { c1=''; c2='' c3=''; c4='' c5=''; c6='' c7=''; c8='' } # Store the output of 'uname' to avoid calling it multiple times # throughout the script. 'read </dev/null && info_length=$((${#info} > info_length ? ${#info} : info_length)) done # Add an additional space of length to act as a gap. : $((info_length+=1)) # Iterate over the above list and run any existing "get_" functions. for info; do "get_$info"; done } # Position the cursor below both the ascii art and information lines # according to the height of both. If the information exceeds the ascii # art in height, don't touch the cursor, else move it down N lines. cursor_pos=$((info_height > ascii_height ? 0 : ascii_height - info_height)) # Print '$cursor_pos' amount of newlines to correctly position the # cursor. This used to be a 'printf $(seq X X)' however 'seq' is only # typically available (by default) on GNU based systems! while [ "${i:-0}" -le "$cursor_pos" ]; do printf '\n' : $((i+=1)) done >&6 } main "$@"