-2

After some reading somewhere on this interwebs, I found that it was best practice to to the following when using the output of a command as a variable:

FILE_CORE_NAME="$(/usr/bin/env basename $(/usr/bin/env awk -F "." '{print $1}' <<< "${FILE_TO_PROCESS}"))"; local FILE_CORE_NAME;

However, when I use this configuration the value derived for FILE_CORE_NAME is blank. If I remove the trailing local FILE_CORE_NAME from the line, everything works as expected.

Is what I read wrong or am I doing it wrong?

terdon
  • 242,166
  • 1
    Please give us some context. It is very unlikely that something this complex will be best practice, and using CAPS in variable names is bad practice to begin with unless you are creating new global environment variables. So please give us a more complete example, explain what you want to do (why are you using env for example, that makes no sense there). None of what you show seems to be a very good choice but it's hard to give you a better option without more information. I can tell you that local var will overwrite any value you originally had. – terdon Nov 09 '23 at 13:27
  • For the issue with local, what you have on the right-hand side of the assignment isn't relevant. That would be the same issue with just var=foobar; local var. OTOH, for the issue of assigning the output of a command to a variable, the thing with local is not relevant. I'm going to assume your issue is with local, not the command substitution, and that's what the existing answer also seems to assume. You could post another question if there's another matter you wondering. – ilkkachu Nov 09 '23 at 13:36
  • @ilkkachu why did you remove the convoluted command? Yes, it isn't relevant to the local issue, but we don't know which part of all that is what the OP considers "best practice", so it seems a bit excessive to just completely remove it. They were doing things there such as using env outside a script, that suggest they could use mor help than just focusing on local. – terdon Nov 09 '23 at 13:50
  • IMHO, if you can't be certain that awk and basename (standard utilities) are what they seem and you feel that you need to use env to get the utilities from portable paths, then you should also use builtin local in place of local, just in case a user has overloaded that name with a function. Personally, I would drop the use of env and leave it to the user to shoot themselves in the feet. – Kusalananda Nov 09 '23 at 14:37
  • @Kevin, as it stands, your question appears to include multiple issues. It's unclear which one or which ones you're trying to ask about. I suggest refining the question to a single issue, and/or posting multiple questions if you have multiple things in mind. – ilkkachu Nov 09 '23 at 14:45
  • Kevin, what are you trying to achieve here? Can you add some example input and corresponding expected output to your question? – Chris Davies Nov 09 '23 at 15:09
  • @terdon, I tried to explain that in a comment, the one just above yours. I'm getting a bit of a confused impression here, because your question makes it appear you didn't read that comment at all. As mentioned, I didn't think that part was relevant, and they only appeared to mention the issue with local in the text, too. If you disagree with that, you can just say that, without making me reiterate what I've just said. In any case, I was under the impression there was some sort of an idea that questions should only have one, and as it stands, this one could do with some streamlining. – ilkkachu Nov 09 '23 at 15:25
  • Anyway, I'm not sure how one could tell if they're using env within or without a script there, since the post doesn't appear to say where the snippet came from. It's probably not the whole context anyway, since local doesn't work outside a function (at least not in Bash, which is what the tag says, it might be different in other shells.) – ilkkachu Nov 09 '23 at 15:27
  • As the [tag:bash] tag you used instructs - "For shell scripts with errors/syntax errors, please check them with the shellcheck program (or in the web shellcheck server at https://shellcheck.net) before posting here. ". That would've told you about several of your issues. – Ed Morton Nov 10 '23 at 01:48
  • 1
    @edMorton i didnt know that site even existed. thank you! – Kevin Huntly Nov 10 '23 at 13:15
  • You're welcome. It's always good to read the info on any tags you're considering using in your questions. – Ed Morton Nov 10 '23 at 13:41

2 Answers2

3
FILE_CORE_NAME="$(/usr/bin/env basename $(/usr/bin/env awk -F "." '{print $1}' <<< "${FILE_TO_PROCESS}"))"; local FILE_CORE_NAME;

Even when reformatted on several lines for legibility

FILE_CORE_NAME="$(
  /usr/bin/env basename $(
     /usr/bin/env awk -F "." '{print $1}' <<< "${FILE_TO_PROCESS}"
  )
)"
local FILE_CORE_NAME

Is wrong on many accounts:

  • awk runs that {print $1} command for every line of its input, so if you have a $'/a.b/c\nd.e\nf/g.h.i' file for instance, it will output /a<newline>d<newline>f/g'. newline being as valid a character as any and filename not being guaranteed to be text, you shouldn't use line-based text utilities to process them.
  • as hinted above, even for single-line file paths, if you have a /foo.d/file.txt or a /foo.bar.2020.jpg file, you'll get /foo instead of /foo.d/file or /foo.bar.2020. The basename should be done first, and it's the part up to the last . that you want to retrieve.
  • quotes were omitted around the second $(...), so it's subject to split+glob with all the possible catastrophic consequences if can have.
  • the -- option delimiter is missing after basename, so if the file path starts with a - it will fail.
  • local is to declare a variable local to the function. However here, you're defining it local after having modified it, so you've modified the variable of the caller of the function. Then in bash (contrary to ash where the local builtin comes from), unless the locvar_inherit option has been enabled, local var declares the variable local but without any value set.
  • all uppercase variable names should be left for environment variable names or at least those with a global scope throughout the script and dependencies.
  • I can't see the point of using /usr/bin/env here.

Here, the obvious thing to do would be to switch to zsh and do:

local core_name=$FILE_TO_PROCESS:t:r

Where, like in csh from the 70s or vim (and even bash history substitution, but not its parameter expansions), :t gives you the tail and :r the root name (with one extension removed).

If you have to use bash, you can do:

local core_name
core_name=$(basename -- "$FILE_TO_PROCESS") || return
core_name=${core_name%.*}

(which would also work in all Ash like shells that have a local builtin).

Bearing in mind that $(...) removes trailing newline characters.

Beware that if $FILE_TO_PROCESS is for instance /home/me/.zshrc, $core_name will become the empty string. You might want to guard against that and some other special values such as -, . or .. depending on what you want to do with that $core_name in the rest of the function.

2

local name=value creates a local variable and set it to value. In your case there is no value so you create a local variable which is empty and the local variable hides the non-local variable with the same name in which you stored the command output.

So you need to first declare the variable local and then assign it a value.

The following snippet:

func() {
  x=4
  local x
  echo "func: $x"
}
x=1
func
echo "outside: $x"

It prints

func:
outside: 4
ilkkachu
  • 138,973