I am banging my head against the wall about the Bad substitution error in Bash. Consider the following code:
getApiName() {
IFS='-' # hyphen (-) is set as delimiter
read -ra array <<< "$1" # str is read into an array as tokens separated by IFS
for i in "${array[#]}"; do # access each element of array
output+=${i^} #set first letter to uppercase
done
IFS=' '
echo ${output}
}
When I do the following:
getApiName "vl-date-picker"
I get line 21: ${i^}: bad substitution
I have no clue on what's wrong.
Can you guys help me please?
Thanks in advance.
Regards
General Answer
I cannot reproduce your problem. I see two possible reasons:
You are using a non-bash shell.
Check this by adding the command ps to the script and look at the output. If there is no bash in the output, then you are running something different. A shebang #! /bin/bash at the beginning of your script helps to ensure that bash is used but is not a guarantee. ✱
You have an old version of bash which does not support ${i^}
(for instance that 15 (!) year old version pre-installed on Mac OS X).
You can check your bash version using bash --version. ${i^} was introduced in bash 4.0, as can be read here (search for hh. There are new case-modifying word expansions) or here.
Either way, you can use a different command which should work in all Posix shells.
If you have the GNU version of sed (check with sed --version) this command could be
getApiName() {
printf %s "$1" | sed -E 's/(^|-+)(.)/\U\2/g'
}
Nmp-Specific Answer
✱
The documentation of npm-run-script states
The actual shell your script is run within is platform dependent. By default, on Unix-like systems it is the /bin/sh command, on Windows it is the cmd.exe. The actual shell referred to by /bin/sh also depends on the system. As of npm#5.1.0 you can customize the shell with the script-shell configuration.
So to fix your problem you simply have to configure npm such that bash is used instead.
As a workaround, you could also call bash directly in your script. The simplest way to do so is a here-document:
bash -s -- "$#" <<"EOF"
# your original script here
EOF
Related
I want to execute bash scripts that happen to use Windows/CRLF line endings.
I know of the tofrodos package, and how to fromdos files, but if possible, I'd like to run them without any modification.
Is there an environment variable that will force bash to handle CRLF?
Perhaps like this?
dos2unix < script.sh|bash -s
EDIT: As pointed out in the comments this is the better option, since it allows the script to read from stdin by running dos2unix and not bash in a subshell:
bash <(dos2unix < script.sh)
Here's a transparent workaround for you:
cat > $'/bin/bash\r' << "EOF"
#!/bin/bash
script=$1
shift
exec bash <(tr -d '\r' < "$script") "$#"
EOF
This gets rid of the problem once and for all by allowing you to execute all your system's Windows CRLF scripts as if they used UNIX eol (with ./yourscript), rather than having to specify it for each particular invocation. (beware though: bash yourscript or source yourscript will still fail).
It works because DOS style files, from a UNIX point of view, specify the interpretter as "/bin/bash^M". We override that file to strip the carriage returns from the script and run actual bash on the result.
You can do the same for different interpretters like /bin/sh if you want.
I'm trying to write a program that takes a file's name, and puts the date on it. So I'm trying to get substrings for the filename itself, and the extension.
I'm new to BASH so maybe I'm missing something here, but following online guides, it seems like this should work-
#!/bin/bash
echo "Type filename in this dir"
read filename
file=`filename%.*`
end=`filename##*.`
today=`date +%d-%m-%y`
dated="${file}_${today}.${end}"
cat $filename > $dated
But the computer returns these errors-
./fileDater.sh: line 5: filename%.*: command not found
./fileDater.sh: line 6: filename##*.: command not found
I'm using an Ubuntu Subsystem on Windows 10, if that's important.
It seems you've got some confusion about bash substitution; You're trying to execute a command sub-shell instead (eg. `variable##*.`) — it should be using ${ ... } .
#!/bin/bash
echo "Type filename in this dir"
read -r filename
file=${filename%.*}
end=${filename##*.}
today=$(date +%d-%m-%y)
dated="${file}_${today}.${end}"
cat "$filename" > "$dated"
I haven't tried your script, although I believe that is your main issue.
EDIT: Regarding the use of backticks (`...`)
In the form of `COMMAND` it's more or less obsolete for Bash, since it has some trouble with nesting ("inner" backticks need to be escaped) and escaping characters.
Use $(COMMAND) instead — it's also POSIX!
↳ Bash Hackers Wiki - Command Substitution
In my program I need to know the maximum number of process I can run. So I write a script. It works when I run it in shell but but when in program using system("./limit.sh"). I work in bash.
Here is my code:
#/bin/bash
LIMIT=\`ulimit -u\`
ACTIVE=\`ps -u | wc -l \`
echo $LIMIT > limit.txt
echo $ACTIVE >> limit.txt
Anyone can help?
Why The Original Fails
Command substitution syntax doesn't work if escaped. When you run:
LIMIT=\`ulimit -u\`
...what you're doing is running a command named
-u`
...with the environment variable named LIMIT containing the value
`ulimit
...and unless you actually have a command that starts with -u and contains a backtick in its name, this can be expected to fail.
This is because using backticks makes characters which would otherwise be syntax into literals, and running a command with one or more var=value pairs preceding it treats those pairs as variables to export in the environment for the duration of that single command.
Doing It Better
#!/bin/bash
limit=$(ulimit -u)
active=$(ps -u | wc -l)
printf '%s\n' "$limit" "$active" >limit.txt
Leave off the backticks.
Use modern $() command substitution syntax.
Avoid multiple redirections.
Avoid all-caps names for your own variables (these names are used for variables with meaning to the OS or system; lowercase names are reserved for application use).
Doing It Right
#!/bin/bash
exec >limit.txt # open limit.txt as output for the rest of the script
ulimit -u # run ulimit -u, inheriting that FD for output
ps -u | wc -l # run your pipeline, likewise with output to the existing FD
You have a typo on the very first line: #/bin/bash should be #!/bin/bash - this is often known as a "shebang" line, for "hash" (#) + "bang" (!)
Without that syntax written correctly, the script is run through the system's default shell, which will see that line as just a comment.
As pointed out in comments, that also means only the standardised options available to the builtin ulimit command, which doesn't include -u.
I have a shell script that works on Ubuntu and provides me an output as I desire. When I test the same on a slackware linux version, my script fails.
The script fails at:
dialog --title "Test" --gauge "Copying file." 6 100 < <(
rsync -a --progress test.tar.gz /media/sda1 |
unbuffer -p grep -o "[0-9]*%" |
unbuffer -p cut -f1 -d '%'
)
The error is:
Syntax error near unexpected token `<'
What could be different between the two operating systems that the script fails to execute?
The script executes successfully if I get rid of the dialog command and the brackets etc.
Most likely, you are trying to run a bash script with non-bash shell. Or with older bash version.
First, try running it through bash explicitly, i.e.:
bash script.sh
You should also fix your shebang to point at bash:
#!/bin/bash
[Update below]
The < <( ... ) notation is unique for bash and zsh. The syntax error is a clear sign it is not recognised by the slackware shell.
Either slackware does not use bash, or its version of bash is too old for this feature.
Check the value of $BASH_VERSION on both platforms.
A possible alternative for
cat < <(
...
...
)
could be:
cat <<< "$(
...
...
)"
This will work in bash, ksh93, and zsh, and has been around slightly longer.
UPDATE
Based on your feedback, I've looked at the actual pipeline you try to use here.
I believe it's your intention to use column 3 of the --progress output as input for the dialog graphical progress indicator.
I tried this with a directory with lots of small files. Are you aware that this percentage indicator is per file? With my small files, rsync gave only one update per file. As every single file was written in one go, all percentages were equal to 100%.
This command works fine:
$ bash -s stable < <(curl -s https://raw.github.com/wayneeseguin/rvm/master/binscripts/rvm-installer)
However, I don't understand how exactly stable is passed as a parameter to the shell script that is downloaded by curl. That's the reason why I fail to achieve the same functionality from within my own shell script - it gives me ./foo.sh: 2: Syntax error: redirection unexpected:
$ cat foo.sh
#!/bin/sh
bash -s stable < <(curl -s https://raw.github.com/wayneeseguin/rvm/master/binscripts/rvm-installer)
So, the questions are: how exactly this stable param gets to the script, why are there two redirects in this command, and how do I change this command to make it work inside my script?
Regarding the "redirection unexpected" error:
That's not related to stable, it's related to your script using /bin/sh, not bash. The <() syntax is unavailable in POSIX shells, which includes bash when invoked as /bin/sh (in which case it turns off nonstandard functionality for compatibility reasons).
Make your shebang line #!/bin/bash.
Understanding the < <() idiom:
To be clear about what's going on -- <() is replaced with a filename which refers to the output of the command which it runs; on Linux, this is typically a /dev/fd/## type filename. Running < <(command), then, is taking that file and directing it to your stdin... which is pretty close the behavior of a pipe.
To understand why this idiom is useful, compare this:
read foo < <(echo "bar")
echo "$foo"
to this:
echo "bar" | read foo
echo "$foo"
The former works, because the read is executed by the same shell that later echoes the result. The latter does not, because the read is run in a subshell that was created just to set up the pipeline and then destroyed, so the variable is no longer present for the subsequent echo.
Understanding bash -s stable:
bash -s indicates that the script to run will come in on stdin. All arguments, then, are fed to the script in the $# array ($1, $2, etc), so stable becomes $1 when the script fed in on stdin is run.