Pranav
Pranav

Reputation: 2172

Concatenation in shell script and use of 'basename'

I want to read all file names form a particular directory and then create new files with those names by appending some string to them in another directory.

e.g > 'A', 'B', 'C' are in 'logs' directory then script should create 'A_tmp', 'B_tmp', 'C_tmp' in 'tmp' directory

what i am using is -

tempDir=./tmp/
logDir=./logs/

for file in $( find `echo $logDir` -type f )
 do
      name=eval basename $file
      echo $name
      name=$(echo $name | sed 's/.$//')
      echo $tempDir
      opFile=$tempDir$name
      echo $opFile
 done

But what I understood is, $file is containing '\n' as last character and I am unable to concatenate the string.

right now I am not creating files, just printing all the names.

So, how I can remove the '\n' from the file name, and is my understanding correct ?

Upvotes: 2

Views: 10789

Answers (4)

Jonathan Leffler
Jonathan Leffler

Reputation: 754590

Analysis

There are multiple issues to address in your script. Let's take it step by step:

tempDir=./tmp/
logDir=./logs/

for file in $( find `echo $logDir` -type f )

This scheme assumes no spaces in the file names (which is not an unusual restriction; avoiding problems with spaces in names is relatively tricky). Also, there's no need for the echo; just write:

for file in $(find "$logDir" -type f)

Continuing:

do
    name=eval basename $file

This runs the basename command with the environment variable name set to the value eval and the argument $file. What you need here is:

    name=$(basename "$file")

where the double quotes aren't strictly necessary because the name can't contain spaces (but it's not a bad habit to get into to quote all file names because sometimes the names do contain spaces).

    echo $name

This would echo a blank line because name was not set.

    name=$(echo $name | sed 's/.$//')

If name was set, this would chop off the last character, but if the name was A, you'd have nothing left.

    echo $tempDir
    opFile=$tempDir$name
    echo $opFile
done

Give or take double quotes and the fact that you've not added the _tmp suffix to opFile, there's nothing wrong with the rest.

Synthesis

Putting the changes together, you end up with:

tempDir=./tmp/
logDir=./logs/

for file in $(find "$logDir" -type f)
do
    name=$(basename "$file")
    echo "$name"                    # Debug only
    echo "$tempDir"                 # Debug only
    opFile="$tempDir${name}_tmp"
    echo "$opFile"
done

That shows all the intermediate results. You could perfectly well compress that down to:

tempDir=./tmp/
logDir=./logs/

for file in $(find "$logDir" -type f)
do
    opFile="$tempDir"$(basename "$file")"_tmp"
    echo "$opFile"
done

Or, using a simpler combination of double quotes because the names contain no spaces:

tempDir=./tmp/
logDir=./logs/

for file in $(find "$logDir" -type f)
do
    opFile="$tempDir$(basename $file)_tmp"
    echo "$opFile"
done

The echo is there as a surrogate for the copy or move operation you plan to execute, of course.

EDIT: ...and to remove restrictions on file names containing spaces and globbing characters, do it as:

tempDir=./tmp/
logDir=./logs/

find "$logDir" -type f |
while IFS= read -r file
do
    opFile="${tempDir}${file##*/}_tmp"
    echo "$opFile"
done

It will still fail for file names containing newlines. If you want to handle that then investigate a solution using find ... -print0 | xargs -0 or find ... -exec.

Upvotes: 6

udoprog
udoprog

Reputation: 1865

Try the following.

#!/bin/sh

tmpDir=./tmp/
logDir=./logs/

# list all files in log directory, pipe into a loop that reads each path line
# by line..
# Also note that there is no newline in this case since it is swallowed by 'read'.
find $logDir -type f | while read path; do
    # get the basename of the path
    name=`basename $path`
    # copy the found file to the temporary directory.
    dest="$tmpDir/${name}_tmp"
    echo $dest
done

Shell scripts have the ability to concatenate strings easily in statements, as demonstrated with $tmpDir/${name}_tmp, there is no need for replacing the output since read swallows any newlines.

find ... while read is a very useful construct when you want to read multiple lines of anything, it even works for files.

while read line; do
    echo $line
done < filename.txt

Edit: clarified

Upvotes: 2

pbhd
pbhd

Reputation: 4467

If you change

name=eval basename $file

to

name=`eval basename $file`

then afterwads name contains what you want.

Upvotes: 0

Diego Basch
Diego Basch

Reputation: 13079

Try something like this:

tempDir=./tmp/
logDir=./logs/

for file in $( find `echo $logDir` -type f )
  do
    name=`eval basename $file|tr -d "\n"`_tmp
    echo $name
  done

Upvotes: 1

Related Questions