1

I have a text file which is usually filled with multiple lines which I want to "print" with a while loop. The text inside this file contains variables - my problem is, that these variables are not interpreted unlike a similar test-string containing variables stored inside the script. Is it possible to also interpret those variables from my external file or do I have to parse them beforehands etc.? What is the difference between $LINE_INSIDE and $LINE_OUTSIDE? I tried some suggestions from other questions like ${!varialbe_name} and different constructs with quote signs but with no luck so far.

    #!/bin/bash
    # color.sh
    BLUE='\033[1;34m'
    NC='\033[0m' # No Color

    LINE_INSIDE="${BLUE}Blue Text${NC}"
    echo -e ${LINE_INSIDE}

    while read LINE_OUTSIDE; do
            echo -e ${LINE_OUTSIDE}
    done < text_file

Output:

Output of the script

Additional Information: I (indeed) also have shell-commands in my input-text-file which should not by executed. Only the variables should be expaned.

n-tchen
  • 430
  • eval "echo -e ${LINE_OUTSIDE}" – Arkadiusz Drabczyk May 26 '19 at 17:59
  • A better comparison would be LINE_INSIDE='${BLUE}Blue Text${NC}' (single quotes). – muru May 26 '19 at 18:03
  • Please provide a sample set of lines of input data, illustrating variables that must be expanded and commands that must not. – Chris Davies May 31 '19 at 19:59
  • First of all: My primary problem is solved with @Stéphane's answer. Secondly fyi: The input are shell-commands for look-up purposes (a cheat-list) with a caption/description, e.g.: `Mit Grep gefilterten Output an weiteren Befehl weiterleiten ls ${BLUE}/pfad${NC} | grep -e '${GREEN}Regular Expression${NC}' | xargs -I{} sudo mv {} ${BLUE}/pfad${NC}

    Löschen mit Fortschrittsanzeige sudo rm -rv ${BLUE}/pfad${NC} | pv -l -s $(sudo find ${BLUE}/pfad${NC} | pv -l | wc -l) > /dev/null` I should have mentioned it at the first place, so maybe I open a followup question regarding that.

    – n-tchen Jun 01 '19 at 07:46

2 Answers2

7

It would probably make more sense to write it as:

BLUE=$'\033[1;34m'
NC=$'\033[0m' # No Color

eval "cat << EOF
$(<text_file)
EOF
"

than using a while read loop (that's not the right syntax for reading lines btw).

Of course that means that code in there would be interpreted. A $(reboot) in there for instance would cause a reboot, but that's more or less what you're asking for.

That also assumes the text_file doesn't contain an EOF line.

Another approach that would only do variable (environment variable) substitution (and not command substitution for instance) would be to use GNU gettext's envsubst:

BLUE=$'\033[1;34m'
NC=$'\033[0m' # No Color
export BLUE NC
envsubst < text_file

Or so that only those two variables are expanded:

BLUE=$'\033[1;34m'
NC=$'\033[0m' # No Color
export BLUE NC
envsubst '$BLUE$NC' < text_file
  • I indeed have shell-commands in my text file, which should not be executed. I added this information to my original question. So the second part of your answer (export + envsub) (while IFS= read LINE_OUTSIDE; do; echo -e ${LINE_OUTSIDE} | envsubst; done < text_file) is what perfectly solves my problem. – n-tchen May 31 '19 at 16:55
  • @n-tchen that code of yours has a lot of problems, which could be eliminated by reducing it to simply envsubst <text_file. Notice however that envsubst is an obscure, non-standard command -- even perl -pe 's/\$\{(\w+)\}/$ENV{$1}/g' would be much more portable -- and could be easily modified to leave alone the variables it doesn't know about: perl -pe 's|\$\{(\w+)\}|$ENV{$1}//$&|ge'. –  May 31 '19 at 18:01
  • @mosvy I guess I broke the problem too much down. In my actual script I do a couple of steps more (inside the loop) of "formatting" the text-file, so envsubst <text_file isn't an option for me!? Or can I combine this with a loop in an other way like I did? Otherwise it would be a completely new question with "How do I code this more elegante". The script is only for me, so portability isn't important, but I keep your words in mind for other purposes. – n-tchen May 31 '19 at 19:03
  • That's not about elegance. You're omitting the -r option of read and fail to properly quote your variables (which makes the IFS= quite pointless -- unless IFS was really changed in your script, its only purpose is to preserve the trailing spaces from the line, that were already lost in the unquoted ${...}). And the envsubst is one extra fork + exec per line. But as you said, if the script is just for you, I guess that using bash is a form of penitence or masochism, so completely different standards may apply ;-) –  May 31 '19 at 19:45
  • Okay, I understand. The missing -r was just a typo (even though it's, in my case, also working without), but the quotes seems to be a point to think about. Next time my brain is capable enough I will go through your criticism, also regarding using bash. – n-tchen May 31 '19 at 20:06
3

In newer versions of bash (since version 4.4 or so) there's yet another tricky way to indirectly expand variables in strings, without having to go full eval: the @P parameter transformation (= expand as in prompts, including but not limited to variable substitutions).

$ cat file.txt
${BLUE}hello blue${NORM}

$ BLUE=$'\e[34m'
$ NORM=$'\e[m'
$ while IFS= read -r line; do printf '%s\n' "${line@P}"; done < file.txt
hello blue