3 # pfetch - Simple POSIX sh fetch script.
6 # The 'log()' function handles the printing of information.
7 # In 'pfetch' (and 'neofetch'!) the printing of the ascii art and info
8 # happen independently of each other.
10 # The size of the ascii art is stored and the ascii is printed first.
11 # Once the ascii is printed, the cursor is located right below the art
14 # Using the stored ascii size, the cursor is then moved to marker $[2].
15 # This is simply a cursor up escape sequence using the "height" of the
18 # 'log()' then moves the cursor to the right the "width" of the ascii art
19 # with an additional amount of padding to add a gap between the art and
20 # the information (See marker $[3]).
22 # When 'log()' has executed, the cursor is then located at marker $[4].
23 # When 'log()' is run a second time, the next line of information is
24 # printed, moving the cursor to marker $[5].
26 # Markers $[4] and $[5] repeat all the way down through the ascii art
27 # until there is no more information left to print.
29 # Every time 'log()' is called the script keeps track of how many lines
30 # were printed. When printing is complete the cursor is then manually
31 # placed below the information and the art according to the "heights"
34 # The math is simple: move cursor down $((ascii_height - info_height)).
35 # If the aim is to move the cursor from marker $[5] to marker $[6],
36 # plus the ascii height is 8 while the info height is 2 it'd be a move
37 # of 6 lines downwards.
39 # However, if the information printed is "taller" (takes up more lines)
40 # than the ascii art, the cursor isn't moved at all!
42 # Once the cursor is at marker $[6], the script exits. This is the gist
43 # of how this "dynamic" printing and layout works.
45 # This method allows ascii art to be stored without markers for info
46 # and it allows for easy swapping of info order and amount.
48 # $[2] ___ $[3] goldie@KISS
49 # $[4](.ยท | $[5] os KISS Linux
59 # End here if no data was found.
62 # Store the value of '$1' as we reset the argument list below.
65 # Use 'set --' as a means of stripping all leading and trailing
66 # white-space from the info string. This also normalizes all
67 # white-space inside of the string.
69 # Disable the shellcheck warning for word-splitting
70 # as it's safe and intended ('set -f' disables globbing).
71 # shellcheck disable=2046,2086
78 # Move the cursor to the right, the width of the ascii art with an
79 # additional gap for text spacing.
80 printf '
\e[%sC' "${ascii_width--1}"
82 # Print the info name and color the text.
83 printf '
\e[3%s;1m%s
\e[m' "${PF_COL1-4}" "$name"
85 # Print the info name and info data separator.
88 # Move the cursor backward the length of the *current* info name and
89 # then move it forwards the length of the *longest* info name. This
90 # aligns each info data line.
91 printf '
\e[%sD
\e[%sC' "${#name}" "${PF_ALIGN:-$info_length}"
93 # Print the info data, color it and strip all leading whitespace
95 printf '
\e[3%sm%s
\e[m\n' "${PF_COL2-7}" "$info"
97 # Keep track of the number of times 'log()' has been run.
98 info_height=$((${info_height:-0} + 1))
102 # Username is retrieved by first checking '$USER' with a fallback
103 # to the 'id -un' command.
104 user=${USER:-$(id -un)}
106 # Hostname is retrieved by first checking '$HOSTNAME' with a fallback
107 # to the 'hostname' command.
109 # Disable the warning about '$HOSTNAME' being undefined in POSIX sh as
110 # the intention for using it is allowing the user to overwrite the
111 # value on invocation.
112 # shellcheck disable=SC2039
113 hostname=${HOSTNAME:-${hostname:-$(hostname)}}
115 log "
\e[3${PF_COL3:-1}m${user}${c7}@
\e[3${PF_COL3:-1}m${hostname}" " " >&6
119 # This function is called twice, once to detect the distribution name
120 # for the purposes of picking an ascii art early and secondly to display
121 # the distribution name in the info output (if enabled).
123 # On first run, this function displays _nothing_, only on the second
124 # invocation is 'log()' called.
132 # Some Linux distributions (which are based on others)
133 # fail to identify as they **do not** change the upstream
134 # distribution's identification packages or files.
136 # It is senseless to add a special case in the code for
137 # each and every distribution (which _is_ technically no
138 # different from what it is based on) as they're either too
139 # lazy to modify upstream's identification files or they
140 # don't have the know-how (or means) to ship their own
141 # lsb-release package.
143 # This causes users to think there's a bug in system detection
144 # tools like neofetch or pfetch when they technically *do*
145 # function correctly.
147 # Exceptions are made for distributions which are independent,
148 # not based on another distribution or follow different
151 # This applies only to distributions which follow the standard
152 # by shipping unmodified identification files and packages
153 # from their respective upstreams.
154 if command -v lsb_release; then
155 distro=$(lsb_release -sd)
157 # Android detection works by checking for the existence of
158 # the follow two directories. I don't think there's a simpler
160 elif [ -d /system/app ] && [ -d /system/priv-app ]; then
161 distro="Android $(getprop ro.build.version.release)"
164 # This used to be a simple '. /etc/os-release' but I believe
165 # this is insecure as we blindly executed whatever was in the
166 # file. This parser instead simply handles 'key=val', treating
167 # the file contents as plain-text.
168 while IFS='=' read -r key val; do
170 PRETTY_NAME) distro=$val ;;
172 done < /etc/os-release
175 # 'os-release' and 'lsb_release' sometimes add quotes
176 # around the distribution name, strip them.
177 distro=${distro##[\"\']}
178 distro=${distro%%[\"\']}
180 # Special cases for (independent) distributions which
181 # don't follow any os-release/lsb standards whatsoever.
182 command -v crux && distro=$(crux)
183 command -v guix && distro='Guix System'
185 # Check to see if we're running Bedrock Linux which is
186 # very unique. This simply checks to see if the user's
187 # PATH contains a Bedrock specific value.
189 */bedrock/cross/*) distro='Bedrock Linux'
192 # Check to see if Linux is running in Windows 10 under
193 # WSL1 (Windows subsystem for Linux [version 1]) and
194 # append a string accordingly.
196 # If the kernel version string ends in "-Microsoft",
197 # we're very likely running under Windows 10 in WSL1.
198 if [ "$WSLENV" ]; then
199 distro="${distro}${WSLENV+ on Windows 10 [WSL2]}"
201 # Check to see if Linux is running in Windows 10 under
202 # WSL2 (Windows subsystem for Linux [version 2]) and
203 # append a string accordingly.
205 # This checks to see if '$WSLENV' is defined. This
206 # appends the Windows 10 string even if '$WSLENV' is
207 # empty. We only need to check that is has been _exported_.
208 elif [ -z "${kernel%%*-Microsoft}" ]; then
209 distro="$distro on Windows 10 [WSL1]"
214 # Parse the SystemVersion.plist file to grab the macOS
215 # version. The file is in the following format:
217 # <key>ProductVersion</key>
218 # <string>10.14.6</string>
220 # 'IFS' is set to '<>' to enable splitting between the
221 # keys and a second 'read' is used to operate on the
222 # next line directly after a match.
224 # '_' is used to nullify a field. '_ _ line _' basically
225 # says "populate $line with the third field's contents".
226 while IFS='<>' read -r _ _ line _; do
228 # Match 'ProductVersion' and read the next line
229 # directly as it contains the key's value.
231 IFS='<>' read -r _ _ mac_version _
235 done < /System/Library/CoreServices/SystemVersion.plist
237 # Use the ProductVersion to determine which macOS/OS X codename
238 # the system has. As far as I'm aware there's no "dynamic" way
239 # of grabbing this information.
241 10.4*) distro='Mac OS X Tiger' ;;
242 10.5*) distro='Mac OS X Leopard' ;;
243 10.6*) distro='Mac OS X Snow Leopard' ;;
244 10.7*) distro='Mac OS X Lion' ;;
245 10.8*) distro='OS X Mountain Lion' ;;
246 10.9*) distro='OS X Mavericks' ;;
247 10.10*) distro='OS X Yosemite' ;;
248 10.11*) distro='OS X El Capitan' ;;
249 10.12*) distro='macOS Sierra' ;;
250 10.13*) distro='macOS High Sierra' ;;
251 10.14*) distro='macOS Mojave' ;;
252 10.15*) distro='macOS Catalina' ;;
256 distro="$distro $mac_version"
260 # Haiku uses 'uname -v' for version information
261 # instead of 'uname -r' which only prints '1'.
268 # Minix and DragonFly don't support the escape
269 # sequences used, clear the exit trap.
274 # Grab the first line of the '/etc/release' file
275 # discarding everything after '('.
276 IFS='(' read -r distro _ < /etc/release
280 # Show the OpenBSD version type (current if present).
281 # kern.version=OpenBSD 6.6-current (GENERIC.MP) ...
282 IFS=' =' read -r _ distro openbsd_ver _ <<-EOF
283 $(sysctl kern.version)
286 distro="$distro $openbsd_ver"
290 # Catch all to ensure '$distro' is never blank.
291 # This also handles the BSDs.
299 # Don't print kernel output on some systems as the
300 # OS name includes it.
306 # '$kernel' is the cached output of 'uname -r'.
307 log kernel "$kernel" >&6
313 # Despite what these files are called, version doesn't
314 # always contain the version nor does name always contain
316 read -r name < /sys/devices/virtual/dmi/id/product_name
317 read -r version < /sys/devices/virtual/dmi/id/product_version
318 read -r model < /sys/firmware/devicetree/base/model
320 host="$name $version $model"
323 Darwin*|FreeBSD*|DragonFly*)
324 host=$(sysctl -n hw.model)
328 host=$(sysctl -n machdep.dmi.system-vendor \
329 machdep.dmi.system-product)
333 host=$(sysctl -n hw.version)
337 host=$(sysctl -n hw.vendor hw.product)
341 # Turn the host string into an argument list so we can iterate
342 # over it and remove OEM strings and other information which
343 # shouldn't be displayed.
345 # Disable the shellcheck warning for word-splitting
346 # as it's safe and intended ('set -f' disables globbing).
347 # shellcheck disable=2046,2086
354 # Iterate over the host string word by word as a means of stripping
355 # unwanted and OEM information from the string as a whole.
357 # This could have been implemented using a long 'sed' command with
358 # a list of word replacements, however I want to show that something
359 # like this is possible in pure sh.
361 # This string reconstruction is needed as some OEMs either leave the
362 # identification information as "To be filled by OEM", "Default",
363 # "undefined" etc and we shouldn't print this to the screen.
365 # This works by reconstructing the string by excluding words
366 # found in the "blacklist" below. Only non-matches are appended
367 # to the final host string.
369 To | [Bb]e | [Ff]illed | [Bb]y | O.E.M. | OEM |\
370 Not | Applicable | Specified | System | Product | Name |\
371 Version | Undefined | Default | string | INVALID | ๏ฟฝ | os )
379 # '$arch' is the cached output from 'uname -m'.
380 log host "${host:-$arch}" >&6
384 # Uptime works by retrieving the data in total seconds and then
385 # converting that data into days, hours and minutes using simple
389 IFS=. read -r s _ < /proc/uptime
392 Darwin*|*BSD*|DragonFly*)
393 s=$(sysctl -n kern.boottime)
395 # Extract the uptime in seconds from the following output:
396 # [...] { sec = 1271934886, usec = 667779 } Thu Apr 22 12:14:46 2010
400 # The uptime format from 'sysctl' needs to be subtracted from
401 # the current time in seconds.
402 s=$(($(date +%s) - s))
406 # The boot time is returned in microseconds, convert it to
408 s=$(($(system_time) / 1000000))
412 # Split the output of 'kstat' on '.' and any white-space
413 # which exists in the command output.
415 # The output is as follows:
416 # unix:0:system_misc:snaptime 14809.906993005
418 # The parser extracts: ^^^^^
419 IFS=' .' read -r _ s _ <<-EOF
420 $(kstat -p unix:0:system_misc:snaptime)
425 # Grab the uptime in a pretty format. Usually,
426 # 00:00:00 from the 'ps' command.
427 t=$(LC_ALL=POSIX ps -o etime= -p 1)
429 # Split the pretty output into days or hours
430 # based on the uptime.
432 *-*) d=${t%%-*} t=${t#*-} ;;
433 *:*:*) h=${t%%:*} t=${t#*:} ;;
438 # Convert the split pretty fields back into
439 # seconds so we may re-convert them to our format.
440 s=$((${d:-0}*86400 + ${h:-0}*3600 + ${t%%:*}*60 + ${t#*:}))
444 # Convert the uptime from seconds into days, hours and minutes.
445 d=$((s / 60 / 60 / 24))
446 h=$((s / 60 / 60 % 24))
449 # Only append days, hours and minutes if they're non-zero.
450 [ "$d" = 0 ] || uptime="${uptime}${d}d "
451 [ "$h" = 0 ] || uptime="${uptime}${h}h "
452 [ "$m" = 0 ] || uptime="${uptime}${m}m "
454 log uptime "${uptime:-0m}" >&6
458 # This is just a simple wrapper around 'command -v' to avoid
459 # spamming '>/dev/null' throughout this function.
460 has() { command -v "$1" >/dev/null; }
462 # This works by first checking for which package managers are
463 # installed and finally by printing each package manager's
464 # package list with each package one per line.
466 # The output from this is then piped to 'wc -l' to count each
467 # line, giving us the total package count of whatever package
468 # managers are installed.
470 # Backticks are *required* here as '/bin/sh' on macOS is
471 # 'bash 3.2' and it can't handle the following:
477 # shellcheck disable=2006
481 # Commands which print packages one per line.
482 has bonsai && bonsai list
483 has crux && pkginfo -i
484 has pacman-key && pacman -Qq
485 has dpkg && dpkg-query -f '.\n' -W
487 has xbps-query && xbps-query -l
489 has guix && guix package --list-installed
490 has opkg && opkg list-installed
492 # Directories containing packages.
493 has kiss && printf '%s\n' /var/db/kiss/installed/*/
494 has cpt-list && printf '%s\n' /var/db/cpt/installed/*/
495 has brew && printf '%s\n' "$(brew --cellar)/"*
496 has emerge && printf '%s\n' /var/db/pkg/*/*/
497 has pkgtool && printf '%s\n' /var/log/packages/*
498 has eopkg && printf '%s\n' /var/lib/eopkg/package/*
500 # 'nix' requires two commands.
502 nix-store -q --requisites /run/current-system/sw
503 nix-store -q --requisites ~/.nix-profile
508 # Commands which print packages one per line.
509 has pkgin && pkgin list
511 # Directories containing packages.
512 has brew && printf '%s\n' /usr/local/Cellar/*
514 # 'port' prints a single line of output to 'stdout'
515 # when no packages are installed and exits with
516 # success causing a false-positive of 1 package
519 # 'port' should really exit with a non-zero code
520 # in this case to allow scripts to cleanly handle
523 pkg_list=$(port installed)
525 [ "$pkg_list" = "No ports are installed." ] ||
526 printf '%s\n' "$pkg_list"
535 printf '%s\n' /var/db/pkg/*/
543 printf '%s\n' /boot/system/package-links/*
547 printf '%s\n' /usr/pkg/var/db/pkg/*/
551 has pkginfo && pkginfo -i
562 # IRIX's package manager adds 3 lines of extra
563 # output which we must account for here.
564 IRIX) packages=$((packages - 3)) ;;
567 [ "$packages" -gt 1 ] && log pkgs "$packages" >&6
572 # Used memory is calculated using the following "formula":
573 # MemUsed = MemTotal + Shmem - MemFree - Buffers - Cached - SReclaimable
574 # Source: https://github.com/KittyKatt/screenFetch/issues/386
576 # Parse the '/proc/meminfo' file splitting on ':' and 'k'.
577 # The format of the file is 'key: 000kB' and an additional
578 # split is used on 'k' to filter out 'kB'.
579 while IFS=':k ' read -r key val _; do
582 mem_used=$((mem_used + val))
587 mem_used=$((mem_used + val))
590 MemFree|Buffers|Cached|SReclaimable)
591 mem_used=$((mem_used - val))
596 mem_used=$((mem_used / 1024))
597 mem_full=$((mem_full / 1024))
600 # Used memory is calculated using the following "formula":
601 # (wired + active + occupied) * 4 / 1024
603 mem_full=$(($(sysctl -n hw.memsize) / 1024 / 1024))
605 # Parse the 'vmstat' file splitting on ':' and '.'.
606 # The format of the file is 'key: 000.' and an additional
607 # split is used on '.' to filter it out.
608 while IFS=:. read -r key val; do
610 *' wired'*|*' active'*|*' occupied'*)
611 mem_used=$((mem_used + ${val:-0}))
615 # Using '<<-EOF' is the only way to loop over a command's
616 # output without the use of a pipe ('|').
617 # This ensures that any variables defined in the while loop
618 # are still accessible in the script.
623 mem_used=$((mem_used * 4 / 1024))
627 mem_full=$(($(sysctl -n hw.physmem) / 1024 / 1024))
629 # This is a really simpler parser for 'vmstat' which grabs
630 # the used memory amount in a lazy way. 'vmstat' prints 3
631 # lines of output with the needed value being stored in the
634 # This loop simply grabs the 3rd element of each line until
635 # the EOF is reached. Each line overwrites the value of the
636 # previous one so we're left with what we wanted. This isn't
637 # slow as only 3 lines are parsed.
638 while read -r _ _ line _; do
641 # Using '<<-EOF' is the only way to loop over a command's
642 # output without the use of a pipe ('|').
643 # This ensures that any variables defined in the while loop
644 # are still accessible in the script.
650 # Used memory is calculated using the following "formula":
651 # mem_full - ((inactive + free + cache) * page_size / 1024)
653 mem_full=$(($(sysctl -n hw.physmem) / 1024 / 1024))
655 # Use 'set --' to store the output of the command in the
656 # argument list. POSIX sh has no arrays but this is close enough.
658 # Disable the shellcheck warning for word-splitting
659 # as it's safe and intended ('set -f' disables globbing).
660 # shellcheck disable=2046
663 set +f -- $(sysctl -n hw.pagesize \
664 vm.stats.vm.v_inactive_count \
665 vm.stats.vm.v_free_count \
666 vm.stats.vm.v_cache_count)
669 # Calculate the amount of used memory.
671 # $2: vm.stats.vm.v_inactive_count
672 # $3: vm.stats.vm.v_free_count
673 # $4: vm.stats.vm.v_cache_count
674 mem_used=$((mem_full - (($2 + $3 + $4) * $1 / 1024 / 1024)))
678 mem_full=$(($(sysctl -n hw.physmem64) / 1024 / 1024))
680 # NetBSD implements a lot of the Linux '/proc' filesystem,
681 # this uses the same parser as the Linux memory detection.
682 while IFS=':k ' read -r key val _; do
685 mem_free=$((val / 1024))
691 mem_used=$((mem_full - mem_free))
695 # Read the first line of 'sysinfo -mem' splitting on
696 # '(', ' ', and ')'. The needed information is then
697 # stored in the 5th and 7th elements. Using '_' "consumes"
698 # an element allowing us to proceed to the next one.
700 # The parsed format is as follows:
701 # 3501142016 bytes free (used/max 792645632 / 4293787648)
702 IFS='( )' read -r _ _ _ _ mem_used _ mem_full <<-EOF
706 mem_used=$((mem_used / 1024 / 1024))
707 mem_full=$((mem_full / 1024 / 1024))
711 # Minix includes the '/proc' filesystem though the format
712 # differs from Linux. The '/proc/meminfo' file is only a
713 # single line with space separated elements and elements
714 # 2 and 3 contain the total and free memory numbers.
715 read -r _ mem_full mem_free _ < /proc/meminfo
717 mem_used=$(((mem_full - mem_free) / 1024))
718 mem_full=$(( mem_full / 1024))
722 hw_pagesize=$(pagesize)
724 # 'kstat' outputs memory in the following format:
725 # unix:0:system_pages:pagestotal 1046397
726 # unix:0:system_pages:pagesfree 885018
728 # This simply uses the first "element" (white-space
729 # separated) as the key and the second element as the
732 # A variable is then assigned based on the key.
733 while read -r key val; do
735 *total) pages_full=$val ;;
736 *free) pages_free=$val ;;
739 $(kstat -p unix:0:system_pages:pagestotal \
740 unix:0:system_pages:pagesfree)
743 mem_full=$((pages_full * hw_pagesize / 1024 / 1024))
744 mem_free=$((pages_free * hw_pagesize / 1024 / 1024))
745 mem_used=$((mem_full - mem_free))
749 # Read the memory information from the 'top' command. Parse
750 # and split each line until we reach the line starting with
753 # Example output: Memory: 160M max, 147M avail, .....
754 while IFS=' :' read -r label mem_full _ mem_free _; do
757 mem_full=${mem_full%M}
758 mem_free=${mem_free%M}
766 mem_used=$((mem_full - mem_free))
770 log memory "${mem_used:-?}M / ${mem_full:-?}M" >&6
775 # Don't display window manager on macOS.
779 # xprop can be used to grab the window manager's properties
780 # which contains the window manager's name under '_NET_WM_NAME'.
782 # The upside to using 'xprop' is that you don't need to hardcode
783 # a list of known window manager names. The downside is that
784 # not all window managers conform to setting the '_NET_WM_NAME'
787 # List of window managers which fail to set the name atom:
788 # catwm, fvwm, dwm, 2bwm, monster, wmaker and sowm [mine! ;)].
790 # The final downside to this approach is that it does _not_
791 # support Wayland environments. The only solution which supports
792 # Wayland is the 'ps' parsing mentioned below.
794 # A more naive implementation is to parse the last line of
795 # '~/.xinitrc' to extract the second white-space separated
798 # The issue with an approach like this is that this line data
799 # does not always equate to the name of the window manager and
800 # could in theory be _anything_.
802 # This also fails when the user launches xorg through a display
803 # manager or other means.
806 # Another naive solution is to parse 'ps' with a hardcoded list
807 # of window managers to detect the current window manager (based
808 # on what is running).
810 # The issue with this approach is the need to hardcode and
811 # maintain a list of known window managers.
813 # Another issue is that process names do not always equate to
814 # the name of the window manager. False-positives can happen too.
816 # This is the only solution which supports Wayland based
817 # environments sadly. It'd be nice if some kind of standard were
818 # established to identify Wayland environments.
820 # pfetch's goal is to remain _simple_, if you'd like a "full"
821 # implementation of window manager detection use 'neofetch'.
823 # Neofetch use a combination of 'xprop' and 'ps' parsing to
824 # support all window managers (including non-conforming and
825 # Wayland) though it's a lot more complicated!
827 # Don't display window manager if X isn't running.
828 [ "$DISPLAY" ] || return
830 # This is a two pass call to xprop. One call to get the window
831 # manager's ID and another to print its properties.
832 command -v xprop && {
833 # The output of the ID command is as follows:
834 # _NET_SUPPORTING_WM_CHECK: window id # 0x400000
836 # To extract the ID, everything before the last space
838 id=$(xprop -root -notype _NET_SUPPORTING_WM_CHECK)
841 # The output of the property command is as follows:
844 # _NET_WM_NAME = "bspwm"
845 # _NET_SUPPORTING_WM_CHECK: window id # 0x400000
846 # WM_CLASS = "wm", "Bspwm"
848 # To extract the name, everything before '_NET_WM_NAME = \"'
849 # is removed and everything after the next '"' is removed.
850 wm=$(xprop -id "$id" -notype -len 25 -f _NET_WM_NAME 8t)
853 # Handle cases of a window manager _not_ populating the
854 # '_NET_WM_NAME' atom. Display nothing in this case.
857 wm=${wm##*_NET_WM_NAME = \"}
862 # Fallback to checking the process list
863 # for the select few window managers which
864 # don't set '_NET_WM_NAME'.
865 while read -r ps_line; do
871 *monsterwm*) wm=monsterwm ;;
872 *wmaker*) wm='Window Maker' ;;
888 # This only supports Xorg related desktop environments though
889 # this is fine as knowing the desktop environment on Windows,
890 # macOS etc is useless (they'll always report the same value).
892 # Display the value of '$XDG_CURRENT_DESKTOP', if it's empty,
893 # display the value of '$DESKTOP_SESSION'.
894 log de "${XDG_CURRENT_DESKTOP:-$DESKTOP_SESSION}" >&6
898 # Display the basename of the '$SHELL' environment variable.
899 log shell "${SHELL##*/}" >&6
903 # Display the value of '$VISUAL', if it's empty, display the
904 # value of '$EDITOR'.
905 log editor "${VISUAL:-$EDITOR}" >&6
909 # Print the first 8 terminal colors. This uses the existing
910 # sequences to change text color with a sequence prepended
911 # to reverse the foreground and background colors.
913 # This allows us to save hardcoding a second set of sequences
914 # for background colors.
915 palette="
\e[7m$c1 $c1 $c2 $c2 $c3 $c3 $c4 $c4 $c5 $c5 $c6 $c6
\e[m"
917 # Print the palette with a new-line before and afterwards.
924 # This is a simple function to read the contents of
925 # an ascii file from 'stdin'. It allows for the use
926 # of '<<-EOF' to prevent the break in indentation in
929 # This function also sets the text colors according
930 # to the ascii color.
932 # 'PF_COL1': Set the info name color according to ascii color.
933 # 'PF_COL3': Set the title color to some other color. ยฏ\_(ใ)_/ยฏ
934 PF_COL1=${PF_COL1:-${1:-7}}
935 PF_COL3=${PF_COL3:-$((${1:-7}%8+1))}
937 # POSIX sh has no 'var+=' so 'var=${var}append' is used. What's
938 # interesting is that 'var+=' _is_ supported inside '$(())'
939 # (arithmetic) though there's no support for 'var++/var--'.
941 # There is also no $'\n' to add a "literal"(?) newline to the
942 # string. The simplest workaround being to break the line inside
943 # the string (though this has the caveat of breaking indentation).
944 while IFS= read -r line; do
950 # This checks for ascii art in the following order:
951 # '$1': Argument given to 'get_ascii()' directly.
952 # '$PF_ASCII': Environment variable set by user.
953 # '$distro': The detected distribution name.
954 # '$os': The name of the operating system/kernel.
956 # NOTE: Each ascii art below is indented using tabs, this
957 # allows indentation to continue naturally despite
958 # the use of '<<-EOF'.
959 case ${1:-${PF_ASCII:-${distro:-$os}}} in
978 ${c2}'-----------------'
1001 ${c4} / / _____\\ \\
1002 ${c4}/_/ \`----.\\_\\
1039 ${c2} ____${c3}^${c5}____
1040 ${c2} |\\ ${c3}|${c5} /|
1041 ${c2} | \\ ${c3}|${c5} / |
1042 ${c5}<---- ${c4}---->
1043 ${c4} | / ${c2}|${c3} \\ |
1044 ${c4} |/__${c2}|${c3}__\\|
1086 ${c1}/${c4}/ \\${c6}\\
1087 ${c1}/${c4}/ \\ ${c6}\\
1088 ${c1}/ ${c4}/ _) ${c6})
1089 ${c1}/_${c4}/___-- ${c6}__-
1099 ${c4}__${c7}_| |_${c4}_/ /${c7}
1100 ${c4}/ ${c7}(_ _)${c4}_/${c7}
1102 ${c4}\\ \\${c7}__/ |
1107 [Ff]ree[Bb][Ss][Dd]*)
1109 ${c1}/\\,-'''''-,/\\
1132 ${c2} _-\`\`-, ,-\`\`-_
1133 ${c2} .' _-_| |_-_ '.
1134 ${c2}./ /_._ _._\\ \\.
1135 ${c2}: _/_._\`:'_._\\_ :
1136 ${c2}\\:._/ ,\` \\ \\ \\_.:/
1137 ${c2} ,-';'.@) \\ @) \\
1138 ${c2} ,'/' ..- .\\,-.|
1139 ${c2} /'/' \\(( \\\` ./ )
1140 ${c2} '/'' \\_,----'
1146 [Gg]uix[Ss][Dd]*|[Gg]uix*)
1195 [Kk][Dd][Ee]*[Nn]eon*)
1197 ${c7} .${c6}__${c7}.${c6}__${c7}.
1198 ${c6} / _${c7}.${c6}_ \\
1200 ${c7} . ${c6}| ${c7}O${c6} | ${c7}.
1201 ${c6} \\ \\_${c7}.${c6}_/ /
1202 ${c6} \\${c7}.${c6}__${c7}.${c6}__${c7}.${c6}/
1206 [Ll]inux*[Ll]ite*|[Ll]ite*)
1210 ${c3} / ${c7}/ ${c3}/
1211 ${c3}> ${c7}/ ${c3}/
1212 ${c3}\\ ${c7}\\ ${c3}\\
1213 ${c3}\\_${c7}\\${c3}_\\
1218 [Ll]inux*[Mm]int*|[Mm]int)
1222 ${c2}| ${c7}| _____ ${c2}|
1223 ${c2}| ${c7}| | | | ${c2}|
1224 ${c2}| ${c7}| | | | ${c2}|
1225 ${c2}| ${c7}\\__${c7}___/ ${c2}|
1234 ${c4}(${c7}.. ${c4}|
1235 ${c4}(${c5}<> ${c4}|
1236 ${c4}/ ${c7}__ ${c4}\\
1237 ${c4}( ${c7}/ \\ ${c4}/|
1238 ${c5}_${c4}/\\ ${c7}__)${c4}/${c5}_${c4})
1239 ${c5}\/${c4}-____${c5}\/
1243 [Mm]ac[Oo][Ss]*|[Dd]arwin*)
1247 ${c2} .'\`_\`-'_\`\`.
1282 ${c4};${c7},${c4} ', ,' ${c7},${c4};
1283 ${c4}; ${c7}',${c4} ',,' ${c7},'${c4} ;
1284 ${c4}; ${c7}',${c4} ${c7},'${c4} ;
1285 ${c4}; ${c7};, '' ,;${c4} ;
1286 ${c4}; ${c7};${c4};${c7}',,'${c4};${c7};${c4} ;
1287 ${c4}', ${c7};${c4};; ;;${c7};${c4} ,'
1288 ${c4} '${c7};${c4}' '${c7};${c4}'
1304 [Nn]et[Bb][Ss][Dd]*)
1306 ${c7}\\\\${c3}\`-______,----__
1307 ${c7} \\\\ ${c3}__,---\`_
1308 ${c7} \\\\ ${c3}\`.____
1309 ${c7} \\\\${c3}-______,----\`-
1319 ${c4} ==\\\\__\\\\/ //
1323 ${c4}// /\\\\ \\\\==
1328 [Oo]pen[Bb][Ss][Dd]*)
1333 ${c3} | ${c7}O O${c3} |
1340 [Oo]pen[Ss][Uu][Ss][Ee]*[Tt]umbleweed*)
1343 ${c2} / ____\\ / ____ \\
1344 ${c2}/ / \`/ / \\ \\
1345 ${c2}\\ \\____/ /,____/ /
1346 ${c2} \\______/ \\_____/
1350 [Oo]pen[Ss][Uu][Ss][Ee]*|[Oo]pen*SUSE*|SUSE*|suse*)
1365 ${c1}| |.-----.-----.-----.
1366 ${c1}| - || _ | -__| |
1367 ${c1}|_______|| __|_____|__|__|
1368 ${c1} ________|__| __
1369 ${c1}| | | |.----.| |_
1370 ${c1}| | | || _|| _|
1371 ${c1}|________||__| |____|
1378 ${c5}.\`_//_//_/ / \`.
1390 ${c6}\\ \\ \\ \\ / /
1391 ${c6}\\ \\_\\ \\ / /
1405 ${c7}| |_________| |
1406 ${c7}|_____________|
1433 [Ss]un[Oo][Ss]|[Ss]olaris*)
1461 ${c2}| \\______ \\_|
1467 # On no match of a distribution ascii art, this function calls
1468 # itself again, this time to look for a more generic OS related
1469 # ascii art (KISS Linux -> Linux).
1475 printf 'error: %s is not currently supported.\n' "$os" >&6
1476 printf 'error: Open an issue for support to be added.\n' >&6
1481 # Store the "width" (longest line) and "height" (number of lines)
1482 # of the ascii art for positioning. This script prints to the screen
1483 # *almost* like a TUI does. It uses escape sequences to allow dynamic
1484 # printing of the information through user configuration.
1486 # Iterate over each line of the ascii art to retrieve the above
1487 # information. The 'sed' is used to strip '
\e[3Xm' color codes from
1488 # the ascii art so they don't affect the width variable.
1489 while read -r line; do
1490 ascii_height=$((${ascii_height:-0} + 1))
1492 # This was a ternary operation but they aren't supported in
1494 [ "${#line}" -gt "${ascii_width:-0}" ] &&
1495 ascii_width=${#line}
1497 # Using '<<-EOF' is the only way to loop over a command's
1498 # output without the use of a pipe ('|').
1499 # This ensures that any variables defined in the while loop
1500 # are still accessible in the script.
1502 $(printf %s "$ascii" | sed 's/
\e\[3.m//g')
1505 # Add a gap between the ascii art and the information.
1506 ascii_width=$((ascii_width + 4))
1508 # Print the ascii art and position the cursor back where we
1509 # started prior to printing it.
1510 # '[1m': Print the ascii in bold.
1512 # '[%sA': Move the cursor up '$ascii_height' amount of lines.
1513 printf '
\e[1m%s
\e[m
\e[%sA' "$ascii" "$ascii_height" >&6
1517 [ "$1" = --version ] && {
1518 printf 'pfetch 0.7.0\n'
1522 # Hide 'stderr' unless the first argument is '-v'. This saves
1523 # polluting the script with '2>/dev/null'.
1524 [ "$1" = -v ] || exec 2>/dev/null
1526 # Hide 'stdout' and selectively print to it using '>&6'.
1527 # This gives full control over what it displayed on the screen.
1528 exec 6>&1 >/dev/null
1530 # Allow the user to execute their own script and modify or
1531 # extend pfetch's behavior.
1532 # shellcheck source=/dev/null
1533 . "${PF_SOURCE:-/dev/null}" ||:
1535 # Ensure that the 'TMPDIR' is writable as heredocs use it and
1536 # fail without the write permission. This was found to be the
1537 # case on Android where the temporary directory requires root.
1538 [ -w "${TMPDIR:-/tmp}" ] || export TMPDIR=~
1540 # Generic color list.
1541 # Disable warning about unused variables.
1542 # shellcheck disable=2034
1544 c1='
\e[31m'; c2='
\e[32m'
1545 c3='
\e[33m'; c4='
\e[34m'
1546 c5='
\e[35m'; c6='
\e[36m'
1547 c7='
\e[37m'; c8='
\e[38m'
1550 # Avoid text-wrapping from wrecking the program output.
1552 # Some terminals don't support these sequences, nor do they
1553 # silently conceal them if they're printed resulting in
1554 # partial sequences being printed to the terminal!
1555 [ "$TERM" = dumb ] ||
1556 [ "$TERM" = minix ] ||
1557 [ "$TERM" = cons25 ] || {
1558 # Disable line-wrapping.
1561 # Enable line-wrapping again on exit.
1562 trap 'printf
\e[?7h >&6' EXIT
1565 # Store the output of 'uname' to avoid calling it multiple times
1566 # throughout the script. 'read <<EOF' is the simplest way of reading
1567 # a command into a list of variables.
1568 read -r os kernel arch <<-EOF
1572 # Always run 'get_os' for the purposes of detecting which ascii
1576 # Allow the user to specify the order and inclusion of information
1577 # functions through the 'PF_INFO' environment variable.
1578 # shellcheck disable=2086
1580 # Disable globbing and set the positional parameters to the
1581 # contents of 'PF_INFO'.
1583 set +f -- ${PF_INFO-ascii title os host kernel uptime pkgs memory}
1585 # Iterate over the info functions to determine the lengths of the
1586 # "info names" for output alignment. The option names and subtitles
1587 # match 1:1 so this is thankfully simple.
1589 command -v "get_$info" >/dev/null || continue
1591 # This was a ternary operation but they aren't supported in
1593 [ "${#info}" -gt "${info_length:-0}" ] &&
1594 info_length=${#info}
1597 # Add an additional space of length to act as a gap.
1598 info_length=$((info_length + 1))
1600 # Iterate over the above list and run any existing "get_" functions.
1601 for info; do "get_$info"; done
1604 # Position the cursor below both the ascii art and information lines
1605 # according to the height of both. If the information exceeds the ascii
1606 # art in height, don't touch the cursor (0/unset), else move it down
1609 # This was a ternary operation but they aren't supported in Minix's shell.
1610 [ "${info_height:-0}" -lt "${ascii_height:-0}" ] &&
1611 cursor_pos=$((ascii_height - info_height))
1613 # Print '$cursor_pos' amount of newlines to correctly position the
1614 # cursor. This used to be a 'printf $(seq X X)' however 'seq' is only
1615 # typically available (by default) on GNU based systems!
1616 while [ "${i:=0}" -le "${cursor_pos:-0}" ]; do