4
\$\begingroup\$

I have recently written some code in Bash where I'm starting to program some things and I think I'm overcomplicating the script I wrote.

I'd like to make it simpler so that in future I would avoid these mistakes.

I've posted this on Stack Overflow and one user did reply that I can "simplify" multi-echoing:

just use 1 echo command, open with a double quote on the first line and close the quote after the last one

Here's the code:

#!/bin/bash

# Look for path when the skript is
SCRIPT_PATH=$(readlink -f "$0")

SEARCH=$(dirname "$SCRIPT_PATH")"/" 
REPLACE="../../"

echo
echo \ \ START - Replacing Absolute path to Relative
echo \ \ =================================================================
echo \ \ Info: Automatic replacement of the \>absolute\< path 
echo \ \ to \>relative\< for the Pedestrian Tool positioning result files
echo \ \ =================================================================
echo
echo \ \ "Searching for >>  $SEARCH"
echo \ \ "Replacing by  >>  $REPLACE"
echo
echo \ \ "Found in files:"
echo \ \ =================
echo

# grep - filter files where inside is scripts path name and write them down
grep -rl "${SEARCH}" --include \*.pc ./

# replace them with sed ../../
grep -rl "${SEARCH}" --include \*.pc ./ | xargs sed -i "s#${SEARCH}#${REPLACE}#g"

echo
echo \ \ END - Replacing finished...
echo \ \ =================================================================
echo \ \ =================================================================
echo
echo 
echo \ \ START - Renaming Folders by Y-Coordinate of Impactor
echo \ \ =================================================================
echo \ \ Info: Script will look into RESULTS Folder, than find directories
echo \ \ then find files in these directories and from
echo \ \ file named POSITIONING extcarc Y-Coordinate of the Impactor
echo \ \ This value is then used to rename by the: MODEL_Y\$Y-Coord
echo \ \ =================================================================
echo

# $SEARCH=path where the script is + /RESULTS/ where the results are
adresar=$SEARCH"RESULTS/"

directories=$(find $adresar*/ -type d)
echo
echo "Folders in dir: $adresar:"
echo

for d in $directories
do
    echo \ "Operations in folder: $d"

    files=$(find $d -type f )
    for f in $files; do

        fileExtension=${f##*.}

        fullPathFileName=${f%.*}
        fileName=$(basename "$fullPathFileName")

        # if Positioning_<#>.pc file
        # find line that has Target POint Coordinates string
        # example: # Target Point Coordinates  : -934.541, -100.000, 247.510
        # and extract Y-coordinate without float, that is: "-100"
        # can be 600 (without minus)
        if [[ $fileExtension == pc && $fileName == Positioning_* ]]; then
            line=$(grep "Target Point Coordinates" "$f")
            # -o (only matching) -E (extended-regexp) 
            # [^.](without dot)[0-9]+(variable number of numbers)
            STRING=$(echo $line | grep -o -E '[^.][0-9]+')
            N=3
            yCoord=$(echo $STRING | awk -v N=$N '{print $N}')
        fi
    done

    folderName=$(basename "$d" | tr 'a-z' 'A-Z') 

    if [[ $folderName != REPORT ]]; then
        echo \ \ "Dir $d renamed by Y-Coordinates of impactor"
        newFolderName=MODEL_Y$yCoord
        cd $d; cd ..

        mv $(basename "$d") $newFolderName
    fi
    echo
done
echo
echo \ \ END - Renaming complete...
echo \ \ =================================================================
echo \ \ =================================================================
echo
echo
echo \ \ START - Renaming .DSY, .msg, .THP files by folder name
echo \ \ =================================================================
echo \ \ Info: Renames .DSY, .msg, .THP files in folder RESULTS
echo \ \ by the name of parrent folder \(that is named by impactor\'s
echo \ \ Y-Coordinate \(\>\> MODEL_Y300\)
echo \ \ =================================================================
echo
directories=$(find $adresar*/ -type d)

for d in $directories
do
    echo \ "Working in folder: $d"
    files=$(find $d -type f )
    baseName=$(basename "$d")

    for f in $files; do
        cd $d
        fileExtension=${f##*.}

        fullPathFileName=${f%.*}
        fileName=$(basename "$fullPathFileName")

        Name=${baseName}.${fileExtension}  

        if [[ $fileExtension == 'pc' && $fileName != Positioning_* ]]; then
            Name=${baseName}.${fileExtension}
            mv "$f" "$Name"
            echo \ \ "Was renamed to: $Name"

        elif [ $fileExtension == 'DSY' ]; then
            Name=${baseName}.${fileExtension}
            mv "$f" "$Name"
            echo \ \ "Was renamed to: $Name"

        elif [ $fileExtension == 'THP' ]; then
            Name=${baseName}.${fileExtension}
            mv "$f" "$Name"
            echo \ \ "Was renamed to: $Name"

        elif [ $fileExtension == 'msg' ]; then
            Name=${baseName}.${fileExtension}
            mv "$f" "$Name"
            echo \ \ "Was renamed to: $Name"

        else
            echo \ \ "X Wasn't renamed."
        fi
        echo \ "====="
    done
    echo
done
echo \ \ END - Renaming complete...
echo \ \ =================================================================
echo \ \ =================================================================
echo -e "\n"
\$\endgroup\$
2
  • \$\begingroup\$ In addition to answer given by janos, I would consider gathering the if conditions at the end into one. The end has lots of repetitive code, which would have been solved with a function (or similar) in a programming language. But even though using bash, you should avoid repeating code. \$\endgroup\$
    – holroy
    Commented Aug 30, 2015 at 11:28
  • \$\begingroup\$ @holroy What do you mean by that? If fileExtension pc or dsy thp or msg; then Name = base + fileExtension (which I get somehow correspondingly for the right extension)? Or you ment something else. I could write the function for "mv "$f$ "$Name$, echo \ \ "Was..." but I don t know how to modifie the code 'Name=${baseName}.${fileExtension}' to work for the extension. \$\endgroup\$ Commented Aug 31, 2015 at 7:45

1 Answer 1

4
\$\begingroup\$

Simplifying the echo statements

Your question on SO suggests that you're particularly interested in simplifying all those echo statements. A feature called here-documents can help you, for example:

cat <<EOF
  START - Replacing Absolute path to Relative
  =================================================================
  Info: Automatic replacement of the >absolute< path 
  to >relative< for the Pedestrian Tool positioning result files
  =================================================================

  Searching for >>  $SEARCH
  Replacing by  >>  $REPLACE

  Found in files:
  =================
EOF

Notice that this way you can also omit many quotes and escapes.


Speaking of quoting, sometimes you quote too much. For example, instead of this:

SEARCH=$(dirname "$SCRIPT_PATH")"/"

You could write:

SEARCH=$(dirname "$SCRIPT_PATH")/

Danger! Watch out for falling rocks!

Storing the output of find and then iterating over it is error prone. I mean this kind of code:

directories=$(find $adresar*/ -type d)

for d in $directories

The problem with this is that it won't work with subdirectories that have a space in their name. This is because in the for loop, the shell splits the value of $directories by any whitespace character (space, tab, newline), not only newline, as in the output of find.

For example if you have a subdirectory named "a b", find will output it on its own line, but when you iterate over $directories, the shell will split the line of "a b" in the middle.

This splitting is defined by the variable IFS, you can read about it in man bash.

One way to make your commands safe is to set IFS to newline, like this:

oldIFS="$IFS"
IFS=$'\n'

directories=$(find $adresar*/ -type d)

for d in $directories; do
    # do work ...
done

IFS="$oldIFS"
\$\endgroup\$
3
  • \$\begingroup\$ Thank you for your contribution. So what is the right way to iterate over subdirectories if I don't want to change the IFS? \$\endgroup\$ Commented Aug 26, 2015 at 12:38
  • 1
    \$\begingroup\$ The safest way would be using find .... -exec somescript.sh \; but that would not be practical, as you would need to split up the scripts. I don't know a better, practical way then what I proposed, manipulating IFS. It would be an interesting question on UNIX SE \$\endgroup\$
    – janos
    Commented Aug 26, 2015 at 13:25
  • \$\begingroup\$ @SonGokussj4, As an alternative to changing the IFS, you could possibly use find in combinations with xargs, see this answer. \$\endgroup\$
    – holroy
    Commented Aug 30, 2015 at 11:25

Your Answer

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

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