开发者

How to Get a Substring Using Positive and Negative Indexes in Bash

What I want is pretty simple. Given a string 'this is my test string,' I want to return the substring from the 2nd position to the 2nd to last position. Something like: substring 'this is my test string' 1,-1. I know I can get stuff from the begi开发者_运维问答nning of the string using cut, but I'm not sure how I can calculate easily from the end of the string. Help?


Turns out I can do this with awk pretty easily as follows:

echo 'this is my test string' | awk '{ print substr( $0, 2, length($0)-2 ) }'


Be cleaner in awk, python, perl, etc. but here's one way to do it:

#!/usr/bin/bash

msg="this is my test string"
start=2
len=$((${#msg} - ${start} - 2))

echo $len

echo ${msg:2:$len}

results in is is my test stri


You can do this with just pure bash

$ string="i.am.a.stupid.fool.are.you?"
$ echo ${string:  2:$((${#string}-4))}
am.a.stupid.fool.are.yo


Look ma, no global variables or forks (except for the obvious printf) and thoroughly tested:

substring()
{
    # Extract substring with positive or negative indexes
    # @param $1: String
    # @param $2: Start (default start of string)
    # @param $3: Length (default until end of string)

    local -i strlen="${#1}"
    local -i start="${2-0}"
    local -i length="${3-${#1}}"

    if [[ "$start" -lt 0 ]]
    then
        let start+=$strlen
    fi

    if [[ "$length" -lt 0 ]]
    then
        let length+=$strlen
        let length-=$start
    fi

    if [[ "$length" -lt 0 ]]
    then
        return
    fi

    printf %s "${1:$start:$length}"
}


You can do this with pure bash and it works the way it does in languages like Python when you want an end point relative to the end of the string:

 $ x='this is my test string'
 $ echo ${x:1:-1}  # Means "slice x beginning at index 1 to the end, excluding the last character"
 his is my test strin

There are two notes to be made here:

  1. This differs from how bash normally handles substring slicing (normally, the second index is a length, but a negative "length" is treated as an offset from the end of the string)
  2. It's legal to use a negative index as the start position as well, but if you do, you must include a space between the first colon and the negative sign (because :- in parameter expansion is already reserved to mean "Use Default Values", so ${x:-5} means "the value of x if it's set and not null, 5 otherwise", while ${x: -5} means "the last five characters of x").

This can be packaged as a bash function to work exactly the way you want (aside from replacing the , in 1,-1 with a space so they're separate arguments; it could work with comma separated arguments, but it would add complexity), e.g.:

substring() {
  if (($#==3)); then
    # Start and length/end passed
    printf %s "${1:$2:$3}"  # No space needed when negative index from argument
  elif (($#==2)); then
    # Only start passed
    printf %s "${1:$2}"
  else
    printf "%s\n" "Usage: ${FUNCNAME} START [positive LENGTH or negative END]" >&2
    return 1
  fi
}

substring 'this is a test' 1 -1  # Echoes: his is a tes
echo
substring 'this is a test' -4    # Echoes: test

Try it online!

Making an "improved" version that uses purely Python-like semantics (so the third argument is always considered an offset, not a length) is left as an exercise for the reader (it's much more annoying, since negative end indices must remain unconverted, while positive end indices must be converted to lengths or negative offsets, which can cause a cascading requirement to convert negative start indices to positive indices; it's a mess, so I kept the function above simple).

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜