Bash bad substitution with subshell and substring

蹲街弑〆低调 提交于 2019-12-21 03:19:18

问题


A contrived example... given

FOO="/foo/bar/baz"

this works (in bash)

BAR=$(basename $FOO) # result is BAR="baz"
BAZ=${BAR:0:1}       # result is BAZ="b"

this doesn't

BAZ=${$(basename $FOO):0:1} # result is bad substitution

My question is which rule causes this [subshell substitution] to evaluate incorrectly? And what is the correct way, if any, to do this in 1 hop?


回答1:


First off, note that when you say this:

BAR=$(basename $FOO) # result is BAR="baz"
BAZ=${BAR:0:1}       # result is BAZ="b"

the first bit in the construct for BAZ is BAR and not the value that you want to take the first character of. So even if bash allowed variable names to contain arbitrary characters your result in the second expression wouldn't be what you want.

However, as to the rule that's preventing this, allow me to quote from the bash man page:

DEFINITIONS
       The following definitions are used throughout the rest  of  this  docu‐
       ment.
       blank  A space or tab.
       word   A  sequence  of  characters  considered  as a single unit by the
              shell.  Also known as a token.
       name   A word consisting only of  alphanumeric  characters  and  under‐
              scores,  and beginning with an alphabetic character or an under‐
              score.  Also referred to as an identifier.

Then a bit later:

PARAMETERS
       A parameter is an entity that stores values.  It can be a name, a  num‐
       ber, or one of the special characters listed below under Special Param‐
       eters.  A variable is a parameter denoted by a name.  A variable has  a
       value  and  zero or more attributes.  Attributes are assigned using the
       declare builtin command (see declare below in SHELL BUILTIN COMMANDS).

And later when it defines the syntax you're asking about:

   ${parameter:offset:length}
          Substring Expansion.  Expands to  up  to  length  characters  of
          parameter  starting  at  the  character specified by offset.

So the rules as articulated in the manpage say that the ${foo:x:y} construct must have a parameter as the first part, and that a parameter can only be a name, a number, or one of the few special parameter characters. $(basename $FOO) is not one of the allowed possibilities for a parameter.

As for a way to do this in one assignment, use a pipe to other commands as mentioned in other responses.




回答2:


Modified forms of parameter substitution such as ${parameter#word} can only modify a parameter, not an arbitrary word.

In this case, you might pipe the output of basename to a dd command, like

BAR=$(basename -- "$FOO" | dd bs=1 count=1 2>/dev/null)

(If you want a higher count, increase count and not bs, otherwise you may get fewer bytes than requested.)

In the general case, there is no way to do things like this in one assignment.




回答3:


It fails because ${BAR:0:1} is a variable expansion. Bash expects to see a variable name after ${, not a value.

I'm not aware of a way to do it in a single expression.




回答4:


As others have said, the first parameter of ${} needs to be a variable name. But you can use another subshell to approximate what you're trying to do.

Instead of:

BAZ=${$(basename $FOO):0:1} # result is bad substitution

Use:

BAZ=$(_TMP=$(basename $FOO); echo ${_TMP:0:1}) # this works



回答5:


A contrived solution for your contrived example:

BAZ=$(expr $(basename $FOO) : '\(.\)')

as in

$ FOO=/abc/def/ghi/jkl
$ BAZ=$(expr $(basename $FOO) : '\(.\)')
$ echo $BAZ
j



回答6:


${string:0:1},string must be a variable name

for example:

FOO="/foo/bar/baz"

baz="foo"

BAZ=eval echo '${'"$(basename $FOO)"':0:1}'

echo $BAZ

the result is 'f'



来源:https://stackoverflow.com/questions/5917439/bash-bad-substitution-with-subshell-and-substring

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!