199

I have a string like this:

/var/cpanel/users/joebloggs:DNS9=domain.example

I need to extract the username (joebloggs) from this string and store it in a variable.

The format of the string will always be the same with exception of joebloggs and domain.example so I am thinking the string can be split twice using cut?

The first split would split by : and we would store the first part in a variable to pass to the second split function.

The second split would split by / and store the last word (joebloggs) into a variable

I know how to do this in PHP using arrays and splits but I am a bit lost in bash.

0

7 Answers 7

500

To extract joebloggs from this string in bash using parameter expansion without any extra processes...

MYVAR="/var/cpanel/users/joebloggs:DNS9=domain.example"

NAME=${MYVAR%:*}  # retain the part before the colon
NAME=${NAME##*/}  # retain the part after the last slash
echo $NAME

Doesn't depend on joebloggs being at a particular depth in the path.


Summary

An overview of a few parameter expansion modes, for reference...

${MYVAR#pattern}     # delete shortest match of pattern from the beginning
${MYVAR##pattern}    # delete longest match of pattern from the beginning
${MYVAR%pattern}     # delete shortest match of pattern from the end
${MYVAR%%pattern}    # delete longest match of pattern from the end

So # means match from the beginning (think of a comment line) and % means from the end. One instance means shortest and two instances means longest.

You can get substrings based on position using numbers:

${MYVAR:3}   # Remove the first three chars (leaving 4..end)
${MYVAR::3}  # Return the first three characters
${MYVAR:3:5} # The next five characters after removing the first 3 (chars 4-9)

You can also replace particular strings or patterns using:

${MYVAR/search/replace}

The pattern is in the same format as file-name matching, so * (any characters) is common, often followed by a particular symbol like / or .

Examples:

Given a variable like

MYVAR="users/joebloggs/domain.example"

Remove the path leaving file name (all characters up to a slash):

echo ${MYVAR##*/}
domain.example

Remove the file name, leaving the path (delete shortest match after last /):

echo ${MYVAR%/*}
users/joebloggs

Get just the file extension (remove all before last period):

echo ${MYVAR##*.}
example

NOTE: To do two operations, you can't combine them, but have to assign to an intermediate variable. So to get the file name without path or extension:

NAME=${MYVAR##*/}      # remove part before last slash
echo ${NAME%.*}        # from the new var remove the part after the last period
domain
Sign up to request clarification or add additional context in comments.

11 Comments

Sweet! And it is done inside the executing shell, thereby way faster than the ones using other commands.
@Fadi You have to switch the wildcard to come before the colon, and use # instead of %. If you want only the part after the very last colon, use ${MYVAR##*:} to get the part after the first colon, use ${MYVAR#*:}
Friend, you don't know how many times I've come back to this answer. Thank you!
Great answer! Question: If my pattern were a variable, would I type it like this ${RET##*$CHOP} or like this ${RET##*CHOP} (or another way)? EDIT: Seems to be the former, ${RET##*$CHOP}
fyi ${MYVAR::3} expression doesn't work in zsh. Returns zsh: closing brace expected. :3:5 or just :3 work fine.
|
66

Define a function like this:

getUserName() {
    echo $1 | cut -d : -f 1 | xargs basename
}

And pass the string as a parameter:

userName=$(getUserName "/var/cpanel/users/joebloggs:DNS9=domain.example")
echo $userName

2 Comments

The only correction I had to do in the above command was removing the ':', like this echo $1 | cut -d -f 1 | xargs. +1 for simple and neat ans.
Please describe the cut -d : -f 1 | xargs basename part of your answer, so that it could be useful to others with similar use cases
28

What about sed? That will work in a single command:

sed 's#.*/\([^:]*\).*#\1#' <<<$string
  • The # are being used for regex dividers instead of / since the string has / in it.
  • .*/ grabs the string up to the last backslash.
  • \( .. \) marks a capture group. This is \([^:]*\).
    • The [^:] says any character _except a colon, and the * means zero or more.
  • .* means the rest of the line.
  • \1 means substitute what was found in the first (and only) capture group. This is the name.

Here's the breakdown matching the string with the regular expression:

        /var/cpanel/users/           joebloggs  :DNS9=domain.example joebloggs
sed 's#.*/                          \([^:]*\)   .*              #\1       #'

Comments

19

Using a single Awk:

... | awk -F '[/:]' '{print $5}'

That is, using as field separator either / or :, the username is always in field 5.

To store it in a variable:

username=$(... | awk -F '[/:]' '{print $5}')

A more flexible implementation with sed that doesn't require username to be field 5:

... | sed -e s/:.*// -e s?.*/??

That is, delete everything from : and beyond, and then delete everything up until the last /. sed is probably faster too than awk, so this alternative is definitely better.

Comments

12

Using a single sed

echo "/var/cpanel/users/joebloggs:DNS9=domain.example" | sed 's/.*\/\(.*\):.*/\1/'

Comments

3

I like to chain together awk using different delimitators set with the -F argument. First, split the string on /users/ and then on :

txt="/var/cpanel/users/joebloggs:DNS9=domain.com"
echo $txt | awk -F"/users/" '{print$2}' | awk -F: '{print $1}'

$2 gives the text after the delim, $1 the text before it.

Comments

2

I know I'm a little late to the party and there's already good answers, but here's my method of doing something like this.

DIR="/var/cpanel/users/joebloggs:DNS9=domain.example"
echo ${DIR} | rev | cut -d'/' -f 1 | rev | cut -d':' -f1

1 Comment

better late than never my friend.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.