I need to concatenate two variables to create a filename that has an underscore. Lets call my variables $FILENAME and $EXTENSION where filename is read from a file.

FILENAME=Hello
EXTENSION=WORLD.txt

Now...

I have tried the following without success:

NAME=${FILENAME}_$EXTENSION
NAME=${FILENAME}'_'$EXTENSION
NAME=$FILENAME\\_$EXTENSION

I always get some kind of weird output. Usually the underscore first.

I need it to be

echo $NAME
Hello_WORLD.txt
  • 2
    Probably your script contains carriage return characters. – Stéphane Chazelas Aug 27 '13 at 20:17
  • Yep that was it. :D – Rhyuk Aug 27 '13 at 20:33
up vote 53 down vote accepted

You can use something like this:

NAME=$(echo ${FILENAME}_${EXTENSION})

This works as well:

NAME=${FILENAME}_${EXTENSION}
  • 1
    The problem was the file where I was reading NAMEs from. Windows formatting... Your solution worked great after that. Thanks – Rhyuk Aug 27 '13 at 20:29
  • 2
    The first command mangles the value of the variable and the use of a command substitution and echo cannot possibly help. The second command was already given in the question. – Gilles Aug 27 '13 at 23:17
  • 1
    Yes! I love it. This was fücking annoying me for the longest while. Thanks for the solution! – racl101 Oct 10 '15 at 19:54

Your:

NAME=${FILENAME}_$EXTENSION
NAME=${FILENAME}'_'$EXTENSION

are all fine, so would be:

NAME=${FILENAME}_${EXTENSION}
NAME=$FILENAME'_'$EXTENSION
NAME=$FILENAME\_$EXTENSION
NAME=$FILENAME"_$EXTENSION"

(but certainly not NAME=$(echo ${FILENAME}_${EXTENSION}) as it uses echo and the split+glob operator).

NAME=$FILENAME_$EXTENSION

would have been the same as:

NAME=${FILENAME_}${EXTENSION}

as _ (contrary to \ or ') is a valid character in a variable name.

Your problem is that you had lines delimited with CRLF instead of just LF, which meant that those variable content ended in CR character.

The CR character, when written to a terminal tells the terminal to move the cursor to the beginning of the line. So Hello<CR>_WORLD.TXT<CR> when sent to a terminal would show as _WORLD.TXT (overriding the Hello).

You should use variables in lowercase (it is the best practice).
So, I'll use filename and extension instead of FILENAME and EXTENSION

As you say that “filename is read from a file”, I'll assume that the script is:

read -r filename  <file.txt
extension=World.txt

And that you want to concatenate both variables $filename and $extension with an underscore _.
The examples you provide (except: no double \) do work correctly here:

name=${filename}_$extension
name=${filename}'_'$extension
name=$filename\_$extension

As some other:

name="${filename}"'_'"${extension}"
name="$filename"'_'"$extension"
name="${filename}_${extension}"

So, your problem is not with how the variables are stick together, but with the contents of the vars. It seems reasonable to think that this:

read -r filename  <file.txt

will read a trailing carriage return \r if reading from a windows file.

One simple solution (for ksh,bash,zsh) is to remove all control characters from the read variable:

filename=${filename//[[:cntrl:]]/}

This could be simulated with a value that has a carriage return:

$ filename=$'Hello\r'
$ echo "starting<$filename>end"
>endting<Hello                       ### note the return to the start of line.
$ echo "starting<${filename//[[:cntrl:]]/}>end"
starting<Hello>end

Or, replacing the value of filename:

$ filename="${filename//[[:cntrl:]]/}"
$ echo "start<$filename>end"
start<Hello>end

Conclusion.

So this:

name="${filename//[[:cntrl:]]/}_${extension//[[:cntrl:]]/}"

Will get the correct value for name even if the other vars contain control characters.

I've switched to using the ${FILENAME}_${EXTENSION} syntax mentioned above.

I used to use $() when I needed to concatenate two variables with an underscore. For example, $YYYYMMDD$()_$HHMMSS to generate a filename containing a timestamp in the format of YYYYMMDD_HHMMSS. The middle $() returns nothing and breaks the two variables apart.

I've used time to measure the assignments using the $YYYYMMDD$()_$HHMMSS method and some of those above, and they've all reported 0ms on the old server I'm using this on. Performance doesn't seem to be an issue.

  • That doesn't answer the question. You should raise a new question for that. $() does fork a subshell in a few shells (some optimise it away) . It's also hijacking an operator for something it's not been designed for. ${x}_y is what you want. – Stéphane Chazelas Oct 30 '17 at 16:49
  • What do you mean, “That doesn’t answer the question.”?   It’s not a great answer, but it is an answer. – Scott Oct 30 '17 at 19:06
  • Edited to remove ambiguity. – user208145 Nov 1 '17 at 0:09

Stéphane Chazelas’s answer,

NAME=${FILENAME}_$EXTENSION

is, of course, the best answer, and should be the accepted answer.  But, in the spirit of user208145’s answer, here’s an alternative:

UNDERSCORE='_'
NAME=$FILENAME$UNDERSCORE$EXTENSION

You can also use this: separate the separate vars with spaces and concatenate them using tr to remove the space.

TableNameToCreate="MyTable"
# concatenation work is done here 
$(echo "$TableNameToCreate _my_other_Info " | tr -d " ")

#Proof
echo "Var to create with var pasted as prefix to suffix
    $(echo "$TableNameToCreate _my_other_Info " | tr -d " ") 
. "

# create a new var
NewVar=$(echo "$TableNameToCreate _my_other_Info " | tr -d " ")

#proof
echo "$NewVar"
  • (1) This question has already been answered more than adequately.  (2) What does your answer do if the existing variable has special characters in it? – G-Man Oct 30 '17 at 17:21
  • This does not address the issue with the variable containing a carriage return. It also removes spaces, something that may not be wanted. – Kusalananda Oct 30 '17 at 17:59
  • @Kusalananda: Actually, unless I’m overlooking something, this handles carriage return just fine; spaces are the only problem. – G-Man Oct 30 '17 at 19:36

Your Answer

 

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

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