0

I have a simple debug_print shell function that I use to help write larger shell scripts. In general, I try to write only Bourne-compatible scripts and avoid Bash-isms. In this case, the Bash version of my debug_print function works fine, but I cannot for the life of me figure out why my Bourne-compatible version fails. Specifically, my problems are with using the %s format modifier with printf.

I've included my test script below. The debug_print function displays a header bar with a label, the contents of its string or file argument, and finally a footer bar with the same width as the header. The problems are with displaying the footer bar and begin with the comment "Display a footer line...". The two uncommented lines following that are what I believe should work, but do not.

Following that are several commented lines under the "None of these work" comment where you can see some of the various tests/variances I used while trying to figure out the problem. After those lines are a working Bourne version, and, finally, a working Bash version.

I've read the manpages in painful detail and tried every variety of format string, quoting, and variable use I can think of. So far, none of these attempts is working right. Under the "None of these work" comment, the none working commands all print out the entire string instead of, as I believe they should, only the number of characters specified by the field width modifier.

To determine "Bourne compatibility" I use dash as /bin/sh on my system, which is typical of Debian systems. Dash is meant for efficient script execution and not interactive use. According to the manpage, it strives for strict Bourne compatibility, so I have reasoned that if my script works with dash then it should be reasonably Bourne compatible and free of bash-isms. Also, I recognize that this function is not the be-all/end-all of debug printing and there are likely numerous corner cases and areas where it could be improved. I welcome all suggestions, but I am particularly interested in this printf problem. Given how simple this should be, it seems like I must be making some stupid mistake, but I cannot spot it.

Here is my debug_print test script:

#!/bin/sh
#
# Purpose: Debug print function.  Outputs delimiting lines and will display
#     either the contents of a file or the contents of a variable.
#
#   Usage: debug_print label input
#     Where label is the title to print in the header bar, usually the name of
#     the input variable.  Input is either a file on disk to be printed, or, 
#     if the argument is not a file then it is assumed to be a string and that
#     will be displayed instead.
#
# Returns: If two parameters are not provided, returns false, otherwise it
#     will always return true.
#
debug_print()
{
    local header_bar header_len dashes

    # Check for arguments
    if [ $# -ne 2 ]; then
        printf "Error: debug_print takes two parameters: 'label' and 'input'\n"
        return 1
    fi

    header_bar="-------------------------$1-------------------------"
    header_len=${#header_bar}
    printf "%s\n" "$header_bar"

    # Display either the contents of the input file or the input string
    if [ -r "$2" ]; then
        cat "$2"
    else
        printf "%s\n" "$2"
    fi

    # Display a footer line with the same length as the header line
    dashes="------------------------------------------------------------"
    printf "%*s\n" "$header_len" "$dashes"

    # None of these work
#    printf "%${header_len}s\n" "$dashes"
#    printf "%${header_len}s\n" "------------------------------------------------------------"
#    printf "%5s\n" xxxxxxxxxxxxxxxxxxxx
#    printf '%5s\n' "xxxxxxxxxxxxxxxxxxxx"
#    xxx="xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
#    printf '%5s\n' $xxx

    # This works with dash
#    i=$header_len
#    while [ $i -gt 0 ]; do
#        printf "-"
#        i=$(( $i - 1 ))
#    done
#    printf "\n"

    # Working bash version
#    printf -v foot_line '%*s' $header_len
#    echo ${foot_line// /-}

    return 0
}


# Test debug printing
debug_print "Label" "The contents of the debug input..."

Thanks for any help.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • @mockinterface As I mentioned in the next paragraph of the post, my not working lines failed by printing the entire input string instead of just a portion of it. Also, which version of dash does Fedora 18 use? On my Debian/testing system, I have version 0.5.7-4 of the dash package installed. – JetpackJohn Mar 09 '14 at 08:15
  • The 0.5.7-4 is the current one on fedora 18 as well at the time of this comment. – mockinterface Mar 09 '14 at 08:37

1 Answers1

5

Are you writing for POSIX compliancy or for Bourne-shell compliancy, those are different things.. dash strives for strict POSIX compliancy, not Bourne compliancy..

Also, variable field widths:

printf "%*s\n" "$header_len" "$dashes"

are not supported in the POSIX shell standard.

To cut string length you could use a dot and a variable in the format field:

printf "%.${header_len}s\n" "$dashes"

--edit--

No provision is made in this volume of POSIX.1-2008 which allows field widths and precisions to be specified as * since the * can be replaced directly in the format operand using shell variable substitution. Implementations can also provide this feature as an extension if they so choose.

http://pubs.opengroup.org/onlinepubs/9699919799/utilities/printf.html#tag_20_94_16

precision Gives the minimum number of digits to appear for the d, o, i, u, x, or X conversion specifiers (the field is padded with leading zeros), the number of digits to appear after the radix character for the e and f conversion specifiers, the maximum number of significant digits for the g conversion specifier; or the maximum number of bytes to be written from a string in the s conversion specifier. The precision shall take the form of a ( '.' ) followed by a decimal digit string; a null digit string is treated as zero.

http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap05.html#tag_05

I did notice one annoying thing and that is that the dot pertains to the number of bytes and not characters, so it is in fact only useable with the standard ASCII character set. So it should work with the dashes (I mean -). There apparently is a bug in the dash man page that mentions characters, but that should be bytes.

Scrutinizer
  • 9,608
  • 1
  • 21
  • 22
  • You are right that they are not at all the same, and rereading the dash manpage, it makes clear that POSIX compliancy is the goal. I use non-Linux systems less and less, but if my aim is to write a more portable script then I suppose POSIX compliancy is the better goal. As for the printf statements, your suggestion works as do the arguments: "%.*s\n" "$header_len" "$dashes". I should have tried a '.', but the manpage says it's optional. According to dash's manpage: "A field width or precision may be ‘*’ instead of a digit string." Maybe one of the Berkeley extensions the manpage mentions? – JetpackJohn Mar 09 '14 at 08:27
  • Hi, yes that is a good goal, since /bin/sh points supposed to a POSIX shell on all modern OS, except Solaris <=10. In Debian an Ubuntu it points to `dash` and on other distributions `bash --posix` gets invoked. The dot is part of the POSIX specification, but the asterisk is not. I have added the relevant bits to my post. I did notice one annoying thing ( and a `bug` in the dash man page ) and that is that the dot pertains to the number of bytes and not characters, so it is in fact only useable with the standard ASCII character set. – Scrutinizer Mar 09 '14 at 09:59
  • @JetpackJohn: see comment. – Scrutinizer Mar 09 '14 at 10:25
  • +1 Thanks for the detailed explaination @Scrutinizer. Used to follow your posts on Unix forums. Glad to have you here on SO. – jaypal singh Mar 09 '14 at 15:59
  • Hi @jaypal, thanks for the kind words, I am more active than ever there, but, I like to come here sometimes also. What was/is your handle there? S.O. does not have PM, does it? – Scrutinizer Mar 09 '14 at 20:19
  • @Scrutinizer No, there is no messaging here. My handle was `Dawn of d Dead` `:)`. Though I never posted a question there, used to get routed to your answers on many occasions via google. So have trolled your answers quite a number of times `;)`. – jaypal singh Mar 09 '14 at 20:41
  • @Scrutinize Thank you for the clarification and the thorough answer. It would seem that, in practice, dash follows the POSIX specs more than the manpage indicates. In any case, following POSIX will be more portable than following the dash manual. Far less chance of using an extension that isn't used elsewhere. – JetpackJohn Mar 13 '14 at 02:53