-2

I have many strings that look like the following:

word1.word2.word3.xyz
word1.word2.word3.word4.abc
word1.word2.mno
word1.word2.word3.pqr

Using bash, I would like to just get the string after the last '.'(dot) character. So the output I want:

xyz
abc
mno
pqr

Is there any way to do this?

Md. Masudur Rahman
  • 1,028
  • 12
  • 30
  • 3
    Please add to your question (no comment): What have you searched for, and what did you find? What have you tried, and how did it fail? – Cyrus Aug 06 '22 at 12:26
  • are these strings in a file? separate entries in an array? stored in a single `bash` variable with a space delimiter? stored in separate `bash` variables? do you need to maintain or replace the original inputs with the desired substring? please update the question with these additional details – markp-fuso Aug 06 '22 at 13:48

3 Answers3

2

AWK will do it. I'm using GNU AWK:

$ awk -F '.' '{print $NF}' <<EOF
word1.word2.word3.xyz
word1.word2.word3.word4.abc
word1.word2.mno
word1.word2.word3.pqr


EOF
xyz
abc
mno
pqr

AWK splits lines into fields and we use -F to set the field separator to .. Fields are indexed from 1, so $1 would get the first one (e.g. word1 in the first line) and we can use the variable $NF (for "number of fields") to get the value of the last field in each line.

https://www.grymoire.com/Unix/Awk.html is a great tutorial on AWK.

You can then just use a for loop to iterate over each of the resulting lines:

$ lines=$(awk -F '.' '{print $NF}' <<EOF
word1.word2.word3.xyz
word1.word2.word3.word4.abc
word1.word2.mno
word1.word2.word3.pqr


EOF
)
$ for line in $lines; do echo $line; done
xyz
abc
mno
pqr

I'm using command substitution here - see the Advanced Bash Scripting Guide for information on loops, command substitution and other useful things.

ndc85430
  • 1,395
  • 3
  • 11
  • 17
  • Thanks for the answer. How do I keep the output of each line in a separate variable, like - for this line 'word1.word2.word3.xyz', var1=xyz? – Md. Masudur Rahman Aug 06 '22 at 12:25
  • Added more to the answer, thanks. If you've not done much shell scripting, then I'd recommend a tutorial (either the guide I linked to, or something else). – ndc85430 Aug 06 '22 at 12:36
  • Please do share the link. I will be a nice help. – Md. Masudur Rahman Aug 06 '22 at 12:42
  • 3
    That TLDP "Advanced Bash Scripting" site is notoriously full of bugs and bad practices, I wouldn't refer anyone to it. See https://github.com/exercism/bash/issues/171 for some better references. – Ed Morton Aug 06 '22 at 12:42
  • @EdMorton do you have a better source? It's what I've used for years sadly! – ndc85430 Aug 06 '22 at 12:43
  • 2
    @ndc85430 see the sites listed under "Instead, consider:" at that link I provided, i.e. http://mywiki.wooledge.org/BashGuide, http://mywiki.wooledge.org/BashFAQ, http://gnu.org/s/bash/manual, http://wiki.bash-hackers.org/. – Ed Morton Aug 06 '22 at 12:45
1

One simple solution would be to split the string on . and then get the last item from the splitted array

lines=(word1.word2.word3.xyz word1.word2.word3.xyz word1.word2.word3.word4.abc word1.word2.mno word1.word2.word3.pqr abcdef  'a * b')

for line in "${lines[@]}"
do
    line_split=(${line//./ })
    echo "${line_split[-1]}"
done

Another clean shell-checked way would be (the idea is the same)

lines=(word1.word2.word3.xyz word1.word2.word3.xyz word1.word2.word3.word4.abc word1.word2.mno word1.word2.word3.pqr abcdef)

for line in "${lines[@]}"; do
    if [[ $line == *.* ]]; then                 # check if line contains dot character
        IFS=. read -r -a split_array <<<"$line" # one-line solution
        echo "${split_array[-1]}"               # shows the results
    else
        echo "No dot in string: $line"
    fi
done
Kiran Parajuli
  • 820
  • 6
  • 14
  • 1
    updated var names. and code resolving all shell-checks. – Kiran Parajuli Aug 06 '22 at 13:01
  • That actually broke your code because now `line_split` would end up with a single entry since it's quoted. shellcheck can suggest valid syntax but it doesn't necessarily understand the semantics of what you're hoping to achieve and that's not how to populate an array with the contents of a variable that you want split at a delimiter. Not sure why you're using a temp array for this at all though rather than just `echo "${line##*.}"`. – Ed Morton Aug 06 '22 at 13:09
  • fixed the code, this seems ok to me. taken from here: https://www.linuxquestions.org/questions/programming-9/bash-shell-script-split-array-383848/#post3270796 – Kiran Parajuli Aug 06 '22 at 13:15
  • I'm sorry but `line_split=(${line//./ })` is also wrong as it exposes the contents of `line` to the shell for evaluation including word splitting, filename expansion, etc. The code you referenced is buggy, don't use it. Try for example `line='a * b'; line_split=(${line//./ }); declare -p line_split` and you'll see that `line_split` now contains the names of every file in your current directory. – Ed Morton Aug 06 '22 at 13:15
  • 1
    Now do `line='a * b'; read -ra line_split <<<"${line//./ }"; declare -p line_split` instead and notice that the `*` remains literal in the output because the quoting protected it from the shell. – Ed Morton Aug 06 '22 at 13:20
  • But again, a simple `echo "${line##*.}"` would make more sense in your loop. – Ed Morton Aug 06 '22 at 13:21
  • Yes true :) if line is `abc` then also it logs `abc`. I'll see more if I can fix this. – Kiran Parajuli Aug 06 '22 at 13:23
  • No `echo "${line##*.}"` would give me `abcdef` if line is `abcdef`. I think we've to check with regex before split. – Kiran Parajuli Aug 06 '22 at 13:25
  • That would be reasonable if that's what the OPs data looked like but it isn't, it always has `.`-something. – Ed Morton Aug 06 '22 at 13:33
1

This is a one-liner solution (after array assignment), without using an explicit loop (but using printf's implicit loop).

arr=( 'word1.word2.word3.xyz'
      'word1.word2.word3.word4.abc'
      'word1.word2.mno'
      'word1.word2.word3.pqr' )

printf '%s\n' "${arr[@]##*.}"
M. Nejat Aydin
  • 9,597
  • 1
  • 7
  • 17