2

I've been working on a way to take a string, set a max character width then split the lines near or at the limit by words and then frame the whole thing.

I managed to do it with printf an array and a for loop, but it's horrible--as rudimentary as it gets*, and I have to do the whole thing each time.

enter image description here

I'd like to make a function and pass the string as an argument. I actually found an example on how to do it, but I wouldn't dare to use the code because I couldn't fully understand it to make it my own which is sort of a must because what I found couldn't be passed as an argument, it was a combination of process redirection and the fmt command. I attempted endlessly without success, then I tried with fold which I found referenced in fmt's manpage.

I think I can do it using Here Documents, redirecting to file, then fmt and printf then later trap to cleanup, but I want to avoid using the filesystem at all costs.

This is what I've got, it's the printMESSAGEBOX function in the code:

#!/usr/bin/env bash # FORMATING: BOX DRAWING boxp=" " ; boxP=" " ; boxt=" ╭────────────────────────────────────────────────────────────────────────╮" ; boxb=" ╰────────────────────────────────────────────────────────────────────────╯" ; boxT="╭─────────────────────────────────────────────────────────────────────────────╮" ; boxB="╰─────────────────────────────────────────────────────────────────────────────╯" ; boxl=" │ " ; boxL="│ " ; boxr=" │" # FORMATING: FOREGROUND/TEXT ñbla () { tput setaf 0;} ; ñred () { tput setaf 1;} ; ñgre () { tput setaf 2;} ; ñyel () { tput setaf 3;} ; ñblu () { tput setaf 4;} ; ñmag () { tput setaf 5;} ; ñcya () { tput setaf 6;} ; ñwhi () { tput setaf 7;} # FORMATING: BACKGROUND/HIGHLIGHTING ññbla () { tput setab 0;} ; ññred () { tput setab 1;} ; ññgre () { tput setab 2;} ; ññyel () { tput setab 3;} ; ññblu () { tput setab 4;} ; ññmag () { tput setab 5;} ; ññcya () { tput setab 6;} ; ññwhi () { tput setab 7;} # FORMATING: MISC (HIGHLIGHT-STANDOUT-ON/HIGHLIGHT-STANDOUT-OFF/RM-STYLE-BOLD-OFF/BOLD-ON/UNDERLINE-ON/UNDERLINE-OFF) ñH () { tput smso;} ; ñh () { tput rmso;} ; ñ0 () { tput sgr0;} ; alias ñb="ñ0" ; ñB () { tput bold;} ; ñU () { tput smul;} ; ñu () { tput rmul;} # EXIT CLEANUP trap ñ0 SIGINT # MESSAGE PRINTING - REVISED CODE printBOXHEADER() { # SORT OUT INPUT while [ "$1" != "" ]; do case "$1" in --type) shift; type=$1 ;; --t0) type=0 ;; # don't print --t1) type=1 ;; # green checkmark --t2) type=2 ;; # red crossmark --t3) type=3 ;; # custom icon --t4) type=4 ;; # custom icon, custom color --ic) shift; ic=$1 ;; --ic-red) ic=red ;; # only obeyed with types 4 --ic-gre) ic=gre ;; # only obeyed with types 4 --ic-yel) ic=yel ;; # only obeyed with types 4 --ic-blu) ic=blu ;; # only obeyed with types 4 --ic-mag) ic=mag ;; # only obeyed with types 4 --ic-cya) ic=cya ;; # only obeyed with types 4 --ic-whi) ic=whi ;; # only obeyed with types 4 --icon) shift; icon=$1 ;; # single character/emoji, # only obeyed with types 3,4 --header) shift; header="$1" ;; # optional esac shift done # DO THAT THING YOU DO if [[ $type -eq 0 ]]; then return 0 elif [[ $type -eq 1 ]]; then echo "[$(ñgre) ✔︎ $(ñ0)] $header" elif [[ $type -eq 2 ]]; then echo "[$(ñred) ✖︎ $(ñ0)] $header" elif [[ $type -eq 3 ]]; then echo "[ $icon ] $header" elif [[ $type -eq 4 ]]; then echo "[$(ñ$ic) $icon $(ñ0)] $header" fi } # MESSAGE PRINTING - HAPHAZARD CODE msgprint() { printf "%s\n" "$boxt" for line in "${msg[@]}"; do printf "%s%s%s%s\n" "$boxl" "$line" "${boxp:${#line}}" "$boxr" done printf "%s\n" "$boxb" } msgprintw() { printf "%s\n" "$boxT" for line in "${msg[@]}"; do printf "%s%s%s%s\n" "$boxL" "$line" "${boxP:${#line}}" "$boxr" done printf "%s\n" "$boxB" } printMESSAGEBOX() { if [[ $widerbox =~ y ]]; then msgprintw elif [[ $widerbox =~ n ]]; then msgprint elif [[ $widerbox =~ x ]]; then return fi } widerbox=n msg=( "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do" "eiusmod tempor incididunt ut labore et dolore magna aliqua." "Pellentesque nec nam aliquam sem et." ) printBOXHEADER "$@" printMESSAGEBOX 

Output:

zx9:Scripting v$ messageBOXREVISITING ╭────────────────────────────────────────────────────────────────────────╮ │ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do │ │ eiusmod tempor incididunt ut labore et dolore magna aliqua. │ │ Pellentesque nec nam aliquam sem et. │ ╰────────────────────────────────────────────────────────────────────────╯ zx9:Scripting v$ 

I want to make it similar like the printBOXHEADER function in the code, it originally had the same mess up structure printMESSAGEBOX has. The leading whitespace is there on purpose, it lines up with printBOXHEADER when it's used, when it's not used, the white space is there to grab your attention. At least that's the intention.

Among the things I tried was trying to use fmt split the lines into the array I've been using so far, then use printf, but I think I got the quoting wrong because it would either pass the whole string or it would split every single word into its own line, framed and not framed in another try. I'm so close to getting it yet still… :(

Anything is welcome and hugely appreciated, even a whole departure from my method (as long as it's still done with with Bash and old commands commonly available on most systems, e.g; fmt…the stuff in [/usr]/bin)

*: in my defense I just started making scripts :) but even with my lack of skills I can tell it's not good.

    2 Answers 2

    2

    You seem to be re-inventing boxes, which is packaged for most linux distros (e.g. apt-get install boxes on debian, ubuntu, etc).

    You could just pipe your text into fmt (or better yet, par) and then into boxes. e.g.

    $ par -w 65 < /tmp/lorem | boxes -d stone +----------------------------------------------------------------+ | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed | | do eiusmod tempor incididunt ut labore et dolore magna aliqua. | | Pellentesque nec nam aliquam sem et. | +----------------------------------------------------------------+ 

    NOTE: boxes does not currently support multi-byte characters in its config file, see their github issue #72. This is unfortunate, because there are lots of nice unicode line-drawing characters available. Hopefully this will eventually get fixed.

    boxes does, however, support multi-byte input characters in its input text. And ANSI escape sequences (e.g. for colours). Neither fmt nor par properly support them, though. Both programs assume that one character = one byte.

    According to wikipedia's par page and the patch for par 1.53 at Add multibyte characters support in par, there is a multi-byte patch for par, but you'll probably have to patch and rebuild it yourself, because it hasn't been merged into the upstream code.

    1
    • Thanks for the tip, I didn't know that and definitely I'm going to try it. That said though… I finally got it and I understand it. :D I'm beyond ecstatic, this had been driving me insane — I'll see if I can edit my question to help others, y'know, explained at my level..subterranean parking 2 or something. :P
      – Vita
      CommentedOct 2, 2022 at 5:25
    0

    I pivoted to refresh other scripts while enlightening came, this lead me to mapfile. It's really quite straightforward.

    You redirect a file to an array.

    mapfile -t <arbitratyNameForArray> < filename.txt

    However, it's the same thing over again. It accepts a file, while I want to inject a variable or something on-the-fly.

    Reading the Bash Reference Manual, a massively helpful ShellCheck Wiki, some random sites of which I lost the links already, etc. I finally got it.

    I just needed to identify better what was redirection and what was substitution, specifically in here:

    mapfile -t <arbitratyNameForArray> < <(some process) 

    The answer is given in the ShellCheck Wiki, two actually, but I still tried to make work what I had to be sure I understand it, coming up with this:

    msg="Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Pellentesque nec nam aliquam sem et. Nisi vitae suscipit tellus mauris a diam maecenas sed enim. Sodales ut etiam sit amet nisl purus. Fermentum posuere urna nec tincidunt praesent. Velit dignissim sodales ut eu sem integer vitae justo. Justo donec enim diam vulputate ut pharetra sit amet." arrayDone=() while IFS='' read -r line; do arrayDone+=("$line") done < <(fmt -w 76 <(echo "$msg")) for arrayLine in "${arrayDone[@]}"; do printf "%s%s%s\n" "⚡️" "$arrayLine" "↩️" done 

    Outputs:

    zx9:Scripting v$ fmtter ⚡️Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod↩️ ⚡️tempor incididunt ut labore et dolore magna aliqua. Pellentesque nec nam↩️ ⚡️aliquam sem et. Nisi vitae suscipit tellus mauris a diam maecenas sed enim.↩️ ⚡️Sodales ut etiam sit amet nisl purus. Fermentum posuere urna nec tincidunt↩️ ⚡️praesent. Velit dignissim sodales ut eu sem integer vitae justo. Justo donec↩️ ⚡️enim diam vulputate ut pharetra sit amet.↩️ zx9:Scripting v$ 

    Now I need to apply it to my real code.

    How it works

    First up is arrayDone=() which is just simply declaring the array before using it. while IFS='' read -r line; do arrayDone+=("$line") done; whilestarts the loops obvi, IFS='' eliminates the space character as word separator, this is I've read endlessly should only be done in a subshell or with some for of isolation otherwise will cause chaos....know what? Forget everything I said, I suck at explaining, so I made a doodle with a comparison to Here Documents + redirection to file, it's much easier to understand. I hope I'm able to help at least one person like so many have helped me here. :)

    Thanks!

    Bash script: split string in lines, then add to array

      You must log in to answer this question.

      Start asking to get answers

      Find the answer to your question by asking.

      Ask question

      Explore related questions

      See similar questions with these tags.