LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Programming (https://www.linuxquestions.org/questions/programming-9/)
-   -   sed-less word wrap in Bash (https://www.linuxquestions.org/questions/programming-9/sed-less-word-wrap-in-bash-4175586953/)

Michael Uplawski 08-13-2016 02:57 AM

sed-less word wrap in Bash
 
1 Attachment(s)
Good morning,

can someone help me compose or develop a command line for a bash-script that will wrap a given expression from "$1" before a given line-limit and at a given delimiter? I have done a lot of experimenting with cut -c and -f and tr to insert line-breaks, for-loops with expr() to break the expression into lines. There have been good results, but some texts just would not be formatted correctly for my uses. The main problem is probably my abstinence from sed.

So the given initial values shall be those in the following example and what I am lacking is the intelligence to make the remainder of the script run correctly.

Code:

expression="$1"
delimiter="$2"
limit="$3"

if [ "$delimiter" == ' ' ]
then
    delimiter=' '
fi
if [ "$limit" == ' ' ]
then
    limit=40
fi

# ATTN! no call to sed below this line
# -----------------------------------
# Thank Youhou.
(...)

For the time, I call from my shell-scripts a small c++ program which does the wrapping, mainly to ensure that text of arbitrary origin is wrapped in a yad generated text-info box.

Otherwise, it is necessary to scroll, and sometimes a long way, to the right of the yad-dialog.

I feel, that the call to the binary “wrap-tool” is a little exaggerated. C/C++ code attached.

grail 08-13-2016 05:18 AM

I think I follow, so maybe something like:
Code:

#!/usr/bin/env bash

expression="$1"
(( $# >= 2 )) && delimiter="$2" || delimiter=' '
(( $# == 3 )) && limit="$3"    || limit=40

# Split data based on delimiter value
IFS="$delimiter"
output=( $expression )
unset IFS

# Only output length of line based on limit
for line in "${output[@]}"
do
  while (( ${#line} > limit ))
  do
    echo "${line:0:limit}"
    line="${line:limit}"
  done
echo $line
done

There are some edge cases you would need to allow for and probably some other error checking at the start, but it should give you an idea :)

Michael Uplawski 08-13-2016 06:01 AM

Quote:

Originally Posted by grail (Post 5590213)
There are some edge cases you would need to allow for and probably some other error checking at the start, but it should give you an idea :)

This is good stuff!
All the elements are there and I do not spot a single ‘sed’. ;)

But I have to admit, that it does the thing “the wrong way around”, probably because of my clumsy description, above. Do not worry, though. Your example motivates me to adapt it to my needs.

Just to show you what I have failed to explain, an example string, processed “both ways”:

Code:

This shall be a longer line of text, athough, ... wait, better this: "Oh but I was so much older then, I'm younger than that now"
If I call your script on the text, I get:
Code:

user@machine:/tmp$ ./wrap_sh "This shall be a longer line of text, although, ... wait, better this: \"Oh but I was so much older then, I'm younger than that now\"" " " 10
This
shall
be
a
longer
line
of
text,
although,
...
wait,
better
this:
"Oh
but
I
was
so
much
older
then,
I'm
younger
than
that
now"

Now the same with my incredibly marvelous wrap-tool:
Code:

user@machine:/tmp$ wrap "This shall be a longer line of text, although, ... wait, better this: \"Oh but I was so much older then, I'm younger than that now\"" " " 10
This shall
be a longer
line of
text, although,
... wait,
better
this: "Oh
but I was
so much
older then,
I'm younger
than that
now"

The difference is that a line-break does always replace a blank, but never before the indicated line-length (10). <-- This phrase is missing from my first post. Worse! I wrote “before a given line-limit”, meaning, that the line must be limited to some upper value, although readability requires that it be wrapped “after a minimum line-length”. The given line-limit is not the third parameter to the script, but the lateral limit of a graphical text box. Sorry.

Michael Uplawski 08-13-2016 07:23 AM

A first working script, not yet thoroughly tested but it produces the same result as the C++-tool from the example-string in the previous post.

Code:

#!/usr/bin/env bash

expression="$1"
(( $# >= 2 )) && delimiter="$2" || delimiter=' '
(( $# == 3 )) && limit="$3"    || limit=40

# Split data based on delimiter value
IFS="$delimiter"
output=( $expression )
unset IFS

# Only output length of line based on limit
out=''
fraction=''
for line in "${output[@]}"
do
        #echo "fraction is $fraction (${#fraction})"

        if [ ${#fraction} -lt $limit ]
        then
                fraction="$fraction$delimiter$line"
        else
                out="$out$fraction"
                fraction="\n$line"
        fi
done
out="$out$fraction"

echo -e "${out:1}"

What bugs me still is the repeated line out="$out$fraction". A footer-controlled loop (do...while) is needed and I will lookup the shell-syntax, once my connection speed is up again (linuxquestions.org is one of the few sites that always charge quickly).

Probably [SOLVED] and thanks again. :)

Ah. Wait. I want to mention, that an alternative approach comprised a $(expr ${#out} % $limit) and I still consider the idea worth some thinking... but alas! It caused some headache, instead.

And... (edit II). Looking at this script solution, I wonder again, if I should not just stick with my wrap-tool anyway.

grail 08-13-2016 08:36 AM

awk, sed, perl or other can probably do this in a cleaner way, but the bash challenge is still a nice solution. I am not sure on the reason for a delimiter if you are going to then ignore it by saying the
limit is the overriding factor. It is probably that I do not understand the real intention of the code or if the delimiter is actually important, maybe just switch the order so length transition is performed first??

Ultimately, if you have a tool that works and no need to incorporate any other bashisms, then probably just use the tool you have :)

danielbmartin 08-13-2016 10:21 AM

Quote:

Originally Posted by grail (Post 5590268)
awk, sed, perl or other can probably do this in a cleaner way ...

Consider the fold command. With this InFile ...
Code:

This shall be a longer line of text, although, ... wait, better this: "Oh but I was so much older then, I'm younger than that now"
... this fold ...
Code:

fold -sw40 $InFile >$OutFile
... produced this OutFile ...
Code:

This shall be a longer line of text,
although, ... wait, better this: "Oh
but I was so much older then, I'm
younger than that now"

Change the 40 to suit your purpose.

Daniel B. Martin

Michael Uplawski 08-13-2016 03:54 PM

Quote:

Originally Posted by danielbmartin (Post 5590301)
Consider the fold command.

It is not necessary anymore, but I confirm that I did not know of the existence of the fold command. -sw40 is all I need.
However, as in this way fold just replaces my own compiled utility, the “courageous shell exercise” taught me something new and was necessary. ;)

This discussion results in a new blog-post.


All times are GMT -5. The time now is 12:23 PM.