13

为 bash 脚本输出帮助文本以正确排列列行的好方法是什么?

就像是:

 Usage: mycommand [options]

    -h| --help           this is some help text.
                         this is more help text.
    -1|--first-option    this is my first option
    -2|--second-option   this is my second option
4

3 回答 3

26

我喜欢用cat这个:

使用.sh

#!/bin/bash

cat <<EOF
Usage: $0 [options]

-h| --help           this is some help text.
                     this is more help text.
-1|--first-option    this is my first option
-2|--second-option   this is my second option
EOF

这将输出:

Usage: usage.sh [options]

-h| --help           this is some help text.
                     this is more help text.
-1|--first-option    this is my first option
-2|--second-option   this is my second option
于 2013-10-22T03:11:54.880 回答
3

我认为这种任务的真正完美解决方案应该比这更复杂。在大多数 shell 中,环境变量 echo${COLUMNS}可用于在脚本中了解终端窗口的宽度。

我为我编写的脚本创建了一个简单的使用函数,并考虑到了这一点${COLUMNS}。在评论中尽可能地解释:

# Put here all the options your script accepts
local options=(
    '-c,--config <FILE>'
    '-l,--list'
    '-r,--run'
    '-v,--verbose'
    '-n,--dry-run'
    '-h,--help'
)
# Put here the corresponding descriptions for every option you specified in the array above
local descriptions=(
    "Use the given configuration file instead of the default one"
    "List all program related files. if used with \`--verbose\`, the full contents are printed"
    "Try to process all the files"
    "Turn on verbose output"
    "don't store files like alwyas but show only what actions would have been taken if $(basename "$0") would have run normally (with or without --run), implies --verbose"
    "display help"
)
# Put here the offset options will get
local options_offset=3
# Put here the offset descriptions will get after the longest option
local descriptions_offset_after_longest_option=5
# Put here the maximum length of descriptions spanning
local maximum_descriptions_length=80

# ---------------------------------
# Up until here is the configuration
# ---------------------------------

# First we print the classic Usage message
echo "Usage: $(basename "$0") [OPTION]..."
# In the next loop, we put in ${max_option_length} the length of the
# longest option. This way, we'll be able to calculate the offset when
# printing long descriptions that should span over several lines.
local max_option_length=1
for (( i = 0; i < ${#options[@]}; i++)); do
    if [[ $max_option_length -lt ${#options[$i]} ]]; then
        max_option_length=${#options[$i]}
    fi
done
# We put in the following variable the total offset of descriptions
# after new-lines.
local descriptions_new_line_offset=$((${max_option_length} + ${options_offset} + ${descriptions_offset_after_longest_option}))
# The next loop is the main loop where we actually print the options with
# the corresponding descriptions.
for (( i = 0; i < ${#options[@]}; i++)); do
    # First, we print the option and the offset we chose above right before it
    printf -- '%*s' ${options_offset}
    printf -- '%s' "${options[$i]}"
    # Here we start tracking through out this loop the current index of the
    # char on the terminal window. This is necessary because in the process
    # of printing the descriptions' words we'll be able to know how not to
    # span over the defined maximum length or not to split words when
    # hitting ${COLUMNS}
    local current_char_index=$((${options_offset} + ${#options[$i]}))
    # We calculate the offset which should be given between the current
    # option and the start of it's description. This is different for every
    # option because every option has a different length but they all must
    # be aligned according to the longest option's length and the offsets
    # we chose above
    local current_description_offset=$((${max_option_length} - ${#options[$i]} + ${descriptions_offset_after_longest_option}))
    # We print this offset before printing the description
    printf -- '%*s' ${current_description_offset}
    # Updating the current_char_index
    current_char_index=$((${current_char_index} + ${current_description_offset}))
    # We put in a temporary variable the current description from the array
    local current_description="${descriptions[$i]}"
    # We divide the current_description to an array with the description's
    # words as the array's elements. This is necessary so we can print the
    # description without spliting words
    IFS=' ' read -r -a description_words <<< "${current_description}"
    # We start a loop for every word in the descriptions words array
    for (( j = 0; j < ${#description_words[@]}; j++)); do
        # We update the current char index before actually printing the
        # next word in the description because of the condition right
        # afterwards
        current_char_index=$((${current_char_index} + ${#description_words[$j]} + 1))
        # We check if the index we will reach will hit the maximum limit we
        # chose in the beginning or the number of ${COLUMNS} our terminal
        # gives us
        if [[ ${current_char_index} -le ${COLUMNS} ]] && [[ ${current_char_index} -le ${maximum_descriptions_length} ]]; then
            # If we don't hit our limit, print the current word
            printf -- '%s ' ${description_words[$j]}
        else
            # If we've hit our limit, print a new line
            printf -- '\n'
            # Print a number of spaces equals to the offset we need to give
            # according to longest option we have and the other offsets we
            # defined above
            printf -- '%*s' ${descriptions_new_line_offset}
            # print the next word in the new line
            printf -- '%s ' ${description_words[$j]}
            # Update the current char index
            current_char_index=$((${descriptions_new_line_offset} + ${#description_words[$j]}))
        fi
    done
    # print a new line between every option and it's description
    printf '\n'
done
于 2018-05-21T15:42:57.337 回答
2

Heredocs 也有一个制表符缩进选项。这允许您在每一行代码前添加任意数量的选项卡 - 这些选项卡将在输出时“吃掉”,从而证明您的输出是正确的。请注意,尾随 'EOF'(在本例中)必须完全左缩进 - 'EOF' 不能是制表符缩进。

小心任何将制表符转换为空格的编辑器(例如,“vi”选项是“expandtab”,它将制表符转换为空格)。不幸的是,多个空格不会像标签那样被“吃掉”。如果您使用“expandtab”(或类似选项)进行代码格式化,那么 heredoc 制表符缩进方法可能对您没有用处。

在下面的示例中,“<<-”是heredoc 遵守制表符缩进的语法。

例子:

    cat <<-EOF
    usage.sh [options]

    -h| --help           this is some help text.
                         this is more help text.
    -1|--first-option    this is my first option
    -2|--second-option   this is my second option
EOF

请注意,“猫”前面有“制表符”,后面的行 - 此处的 HTML 格式可能不允许您剪切粘贴示例。

注意终止的“EOF”是如何左对齐的。

于 2017-01-06T21:58:34.697 回答