I need to write a script to convert a phone number from 123-123-1234 to (123) 123-1234 and 1231231234. I can change all instances of the hyphen with tr command, but how do I change just one of two? I did the second part with this code

echo "Enter phone number (xxx-xxx-xxxx)"
read phone
echo $phone | cat > phone
tr -d "-" < phone

I know there is a better way, but I am a new student and just don't know it yet, Thanks in advance for your help

  • Note that you don't need the cat or the file at all. You can just do echo "$phone" | tr -d '-' directly. Even if you did need a file, cat is pointless, you could just do echo "$phone" > phone directly.
    – terdon
    Nov 17 '19 at 1:18

The title ("How do I change just one character in a string when there more than one of that character?") is your attempted solution but the actual problem is explained in the question body. Compare XY problem. My answer addresses the body.

IFS=- read n1 n2 n3

This will populate n1, n2 and n3 variables with fragments of the user's input; respectively:

  • before the first -,
  • between the first - and the second -,
  • after the second - (including remaining - characters, if any).

Then you can use printf to print them in any format you want, e.g.

printf '(%s) %s-%s\n' "$n1" "$n2" "$n3"
printf '%s%s%s\n' "$n1" "$n2" "$n3"

You may want to be liberal in what you accept and not require a strict format. An example as a shell function:

get_phone_number() {
   IFS= read -rp "Enter phone number (10 digits; additional dashes, spaces, whatever allowed): " p || return 1
   # remove non-digits
   p="$(tr -dc '0123456789' <<< "$p")"
   # check if there is 10 of them
   if [ "${#p}" -ne 10 ]; then
      echo "Wrong number of digits. Aborting." >&2; return 2

Invoke get_phone_number. If the function succeeds then use $n1, $n2 and $n3 with printf (like above) or in whatever way you want.


GNU sed allows specifying which occurence of a pattern (in a line) should be replaced:

First (default)

echo AAAAA | sed 's/A/X/'


echo AAAAA | sed 's/A/X/g'


echo AAAAA | sed 's/A/X/3'

To specifically target your problem with this method, you will need two modifications in sed: 1) change the beginning of the line (^) to add the opening parentheses, 2) replaces the first dash by the closing parentheses and a space.

echo 123-123-1234 | sed 's/^/(/;s/-/) /'
(123) 123-1234

Alternatively, sed allows for matching patterns and saving them (by number of occurrence) by enclosing the pattern in parentheses. In the output string you can call them by their number:

echo 123-123-1234 | sed 's/\([0-9]\+\)-\([0-9]\+\)-\([0-9]\+\)/(\1) \2-\3/'
(123) 123-1234

Here [0-9] matches a digit between 0 and 9, \+ means one or more occurrences of the previous group (GNU extension to sed) and \(<pattern>\) will save the pattern and give it an index number (starting with 1 for the first and so on). Thus \([0-9]\+\)- will match one or more digits followed by a dash and will remember the digits part.

In the output \1 prints the first saved pattern from the match (i.e. [0-9]\+ ... one or more digits before the a dash. We then just use the three saved digit groups and format them as we desire.


You realize you are creating a file called phone right? Also piping echo into cat is pointless, you could (but shouldn't) just do echo "$phone" > phone

I came up with the following for your solution. It may not be the shortest method but it will work:


read -rp "Enter phone number (xxx-xxx-xxxx): " p


printf '%s\n%s\n' \
"($area_code) ${prefix}-${line}" \

Read can prompt the user for input so we can eliminate the echo/read combo.
Then we use parameter expansion to pull the three parts of the phone number out, and print them in the two formats you require.

Note: this doesn't do any sort of sanity checking though so users could provide bogus input and they will get bogus output.

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.