76

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
Rhyuk
  • 1,115

6 Answers6

104

You can use something like this:

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

This works as well:

NAME=${FILENAME}_${EXTENSION}
slm
  • 369,824
Tim
  • 6,141
  • 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 'SO- stop being evil' 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
  • This works for me. When I use '$FILENAME_$EXTENSION', only values of '$EXTENSION' appears. Could you please help explain why this happens? Thanks – Lei Hao Aug 15 '19 at 08:41
16

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).

1

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.

user208145
  • 2,485
0

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.

0

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
-2

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"
Jeff Schaller
  • 67,283
  • 35
  • 116
  • 255
Greg
  • 1