4

I have strings in the form of wva/sia/e1, bct/e2, sv/de/e11. It's always <Part1>/e<NUM> or <Part1>/<Part2>/e<NUM>. What I want is to shorten the strings by keeping the first letters of the parts and ditching the slashes and e:

wva/sia/e1 > ws1
bct/e2 > b2
sv/de/e11 > sd11

How can I do that inside an sh script?

Edit: The string represents a job name:

[...]
job_name=$1 # e.g. 'wva/sia/e1'
job_name=cut_name(job_name) # e.g. 'ws1'
[...]
5

In the form of a script as what you ask for:

#!/usr/bin/env python3
import sys

# read the input, split by /
st = sys.argv[1].split("/")
# get the first char of all sections *but* the last one
# add the last *from* the first character
print("".join([s[0] for s in st][:-1])+st[-1][1:])

Note that this works for any lenght, e.g.:

wva/sia/bct/wva/sia/e1

will become

wsbws1

as long as the last section ends with /e<num>

To use

  1. Copy the script into an empty file, save it as rearrange.py
  2. Run it with the string as argument, e.g.:

    python3 /path/to/rearrange.py wva/sia/e1
    
    > ws1
    

Explanation

The script pretty much explains itself, but also is commented.

| improve this answer | |
  • I thought of doing it in bash, but I also can just call that python script. Thanks! – user1406177 Apr 7 '17 at 21:16
  • @user1406177 you're welcome! Not sure if bash is the most handy tool for this :) – Jacob Vlijm Apr 7 '17 at 21:17
  • Well, bash can do it,too :) – Sergiy Kolodyazhnyy Apr 7 '17 at 22:09
3

Bash 4.3 One-liner

Let's just say that we don't need a full script. Bash has enough capabilities that allow us to get away with a one-liner. Here's one:

bash-4.3$ (read -r var ;IFS='/'; printf "%c" ${var%/*};echo ${var##*[^0-9]}) <<<  "sv/de/e11"
sd11

What is happening ?

  • everything happens in subshell, hence ( ) around the whole command
  • we use here string <<< to send input , and the subshell command gets it via read -r var and stores into var variable
  • we set IFS='/' so that the subshell breaks var into separate items at / separator. This is important for word splitting.
  • next we use suffix removal ${var%/*} to get rid of the last part before / . In the above example it would be e11
  • printf "%c" will see the result of ${var%/*} as sv de due to word splitting and suffix removal mentioned above (magic, yes). Because of how printf words, %c will print only first character, but it will do so for each command-line argument it receives, so for sv de it will output s and d. Printing is done without newline, hence it appears as if characters are typed in sequence
  • echo ${var##*[^0-9]} makes use of prefix removal to get rid of all non-digit characters in the given input string, thus obtaining only the last digits

There's another one-liner approach, which is a bit more explicit and natural to C-like programmers.

bash-4.3$ (read -r inp;IFS='/';arr=( $inp ); for ((i=0;i<$(( ${#arr[@]} -1 ));i++));do printf "%s" ${arr[$i]:0:1};done;printf "%s\n" ${inp##*[^0-9]}) <<<  "sv/de/e11"
sd11

What is this magic ? Here's an explanation:

  • Everything happens in subshell, hence () around the whole command.
  • We use here-string <<< to send the item we want into command's stdin stream, and the command gets it via read -r inp command and stores it into inp variable
  • Next we change IFS variable so that we can break down everything into an array.
  • we iterate over all items until the one before last using C-style for loop for ((initial condition; test condition; post condition)) ; do ... done
  • the $(( ${#arr[@]} - 1 )) is arithmetic expansion where we subtract 1 from the length of the array ${#arr[@]}
  • the printf "%s" ${arr[$i]:0:1} allows us to use parameter expansion where we print only first character of each item, and printf "%s" prints it without newline, thus it appears like we're printing each letter on the same line.
  • finally, once the loop is over, we take original input text and get rid of everything that is non-digit using prefix removal ${#*[^0-9]}

Script approach

Since the question asks for a shell script, here is one in bash 4.3 , which is almost the same approach as above, but more explicit:

#!/bin/bash
IFS='/'
items=( $1 )
counter=1
for i in ${items[@]}
do
    if [ $counter -eq ${#items[@]}  ];
    then
        # note the space before -1
        printf "%s\n" "${i##*[^0-9]}"
    else
        printf "%s" "${i:0:1}"
    fi
    counter=$(($counter + 1)) 
done

The way this works is like so:

  • given a string on command-line as argument, we set internal field separator to / , and allow bash to perform word splitting to break down the string into array called items
  • we iterate over all items in the array ${items[@]} , while keeping track of which item we're at using the counter variable and knowing the number of items in the array (the ${#items[@]} part).
  • The if-statement is what allows us to pick specific character from each item. Using parameter expansion, first character via${i:0:1}. Using longest prefix removal ${variable##prefix}, we remove all non-digit characters from the last string in printf "%s\n" "${i##*[^0-9]}".

Here it is in action:

$ ./shorten_string.sh "wva/sia/e1"                         
ws1
$ ./shorten_string.sh "bct/e2"                             
b2
$ ./shorten_string.sh  "sv/de/e11"                     
sd11
| improve this answer | |
1

OK, not a script, but you can put it in a script (also this is very inelegant since I failed to deal with both forms in one command)

$ sed -r 's:(.).*/(.).*/e([0-9]+):\1\2\3:;s:(.).*/e([0-9]+):\1\2:' file
ws1
b2
sd11

Notes

  • -r use ERE
  • s:old:new: replace old with new
  • .* any number of any characters
  • (.) save one character in this position
  • ([0-9]+) save at least one digit here
  • ; separates commands, like in the shell
  • \1 backreference to characters saved with ()
| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.