How to reverse a list of words in a shell string?

2019-01-23 15:39发布

I have a list of words in a string:

str="SaaaaE SeeeeE SbbbbE SffffE SccccE"

I want to reverse it in order to get

"SccccE SffffE SbbbbE SeeeeE SaaaaE"

How I can do that with ash?

13条回答
走好不送
2楼-- · 2019-01-23 16:29

This is a slight adaptation of the Reverse Characters of Lines script in the GNU sed manual:

#!/usr/bin/sed -f

# Skip lines without blanks
/[[:blank:]]/! b

# Embed line between newlines
s/^.*$/\
&\
/

# Reset the t flag
tx

# Label to jump to
:x

# Swap first and last word between newlines for three or more words
s/\n\([^[:blank:]][^[:blank:]]*\) \(.*\) \([^[:blank:]][^[:blank:]]*\)\n/\3 \
\2\
 \1/

# Jump to label if there was a change on the line
tx

# Swap first and last word between newlines for exactly two words
s/\n\([^[:blank:]][^[:blank:]]*\) \([^[:blank:]][^[:blank:]]*\)\n/\2\
 \
\1/

# Remove the newline markers
s/\n//g

It makes a few assumptions such as no leading or trailing blanks and all the words being separated by exactly one space. Lines with leading or trailing blanks stay untouched, and lines where the words aren't all separated by exactly one space leave the extra spaces where they are, which might not be what is desired.

The script should work with any POSIX conformant sed and basic regular expressions (BRE). Using GNU sed and extended regular expressions (ERE) would allow for better readability, such as

s/\n(\S+) (.*) (\S+)\n/\3 \n\2\n \1/

for the main substitution, but if you're using sed for this problem in the first place, readability is probably not the top concern.

查看更多
对你真心纯属浪费
3楼-- · 2019-01-23 16:33

This works in sh (I don't have ash so can't verify that):

reverse() {
    for (( i = ${#*}; i > 0; i-- ))
    {
        echo ${!i}
    }
}

use it like this:

$ reversed=$(reverse foo bar baz)
$ echo $reversed
baz bar foo

Here are some alternatives. First, one that doesn't use the C-style for:

reverse() {
    while [ ${#*} -gt 0 ]
    do
      echo ${*: -1}
      set -- "${@:1:$(($#-1))}"
    done
}

The next one (from here) works where substitutions don't (e.g. on Android)

reverse() {
    if [ "$#" -gt 0 ]; then
        local arg=$1
        shift
        reverse "$@"
        printf '%s\n' "$arg"
    fi
}

I was trying to find a solution that worked in an Android script, where sh doesn't like C-style for or complex substitution operations. I ended up with the last example because it uses neither.

查看更多
爷的心禁止访问
4楼-- · 2019-01-23 16:34

If using Haskell is acceptable, there is a nice tool called hawk (haskell-awk) which can do this very easily. First you need to install it:

$ cabal install haskell-awk

Then:

$ echo $str | hawk -m reverse

You need ~/.cabal/bin in your PATH to find hawk.

查看更多
We Are One
5楼-- · 2019-01-23 16:36

BASH

This is the function that I use, (not tested in ash)

#reverse_word_order
function rwo(){ tr ' ' '\n'<<<"$@"|tac|tr '\n' ' ';}
echo $(rwo 'Hello There I am You')
#based on https://stackoverflow.com/a/27703859/1153645

DASH

(also works in bash, but some people seem to have an aversion to echo)

rwo(){ echo "$*"|tr ' ' '\n'|tac|tr '\n' ' ';}
查看更多
叛逆
6楼-- · 2019-01-23 16:40

sed grouping:

str="SaaaaE SeeeeE SbbbbE SffffE SccccE"

echo $str | sed 's/\(.* \)\(.* \)\(.* \)\(.* \)\(.*\)/\5 \4\3\2\1/g'
查看更多
聊天终结者
7楼-- · 2019-01-23 16:41

You can use awk as follows:

echo $str | awk '{ for (i=NF; i>1; i--) printf("%s ",$i); print $1; }'
查看更多
登录 后发表回答