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.
Related
SETUP:
A large repository is cloned via 'git' where line endings are adapted automatically. That is, a bash script checked in under Linux appears under Windows as a file with endings 0D0A instead of only 0A. The policy of checkout cannot be modified.
Any script checked out under Windows appears with 0D0A as line endings. Scripts 'source' other scripts, so converting one script does not suffice. Converting all scripts in the repository is not practical.
PROBLEM:
Window's bash does not run on scripts where the line ending is 0D0A. First idea was bash-0d0a converting before execution:
tmp=$(mktemp)
script=$1
shift
awk 1 RS='\r\n' ORS='\n' $script > $tmp
mv $tmp $script
source $script $#
The adapter 'bash-0d0a', however, does not treat 'source-ed' files from within the file to be treated.
QUESTION:
Would it make sense to do:
export PATH=$(dirname path-to-bash-0d0a):$PATH
mv bash-0d0a bash
Or, is it possible, somehow, to let bash do this directly?
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
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.
I'm trying to install RVM. There is a magical command line:
bash < <(curl -s https://rvm.io/install/rvm)
I know what bash and curl are. I know the first < is the I/O redirection. But what does <() syntax mean?
What's the difference between this command and
bash < `curl -s https://rvm.io/install/rvm`
?(the latter command doesn't work)
This is bash's process substitution.
The expression <(list) gets replaced by a file name, either a named FIFO or an entry under /dev/fd. So to actually redirect input from it, you have to use < <(list).
[edit; forgot to answer your second question]
The backticks are called "command substitution". Unlike process substitution, it is part of the POSIX shell specification (i.e., not a bash extension). The shell runs the command in the backticks and substitutes its output on the command line. So this would make sense:
cat < `echo /etc/termcap`
But this would not:
cat < `cat /etc/termcap`
The latter is similar to your example; it tries to use the (complex) output of a command as a file name from which to redirect standard input.
The others have already answered your question very nicely. I'll just add an example to build on them... 99% of the time when I personally use <(), it's to diff the output of two different commands in one shot. For instance,
diff <( some_command ) <( some_other_command )
The syntax for io redirection is
process < file
Hence you need whatever appears after the io redirect to be a filename.
The backtick expansion literally puts the results of the command into the command line. Thus,
`curl -s https://rvm.io/install/rvm`
expands to something like
#!/usr/bin/env bash ...
and the shell would be confused because it would see
bash < #...
instead of a filename.
the <() operator is process substitution, which spawns a new process to run the command within the (..). A new file or pipe is created that will capture the result. The fact that the arrow is pointing left <() instead of >() means that the output from the inner process will be written to the file, which can be read by the process.
In your case, bash < <(...) will be seen as something like bash < /dev/fd/100
If you actually want to see what is going on, run
echo <(curl -s https://rvm.io/install/rvm)
It is called Process Substitution.
I'm trying to read commands from a text file and execute each line from a bash script.
#!/bin/bash
while read line; do
$line
done < "commands.txt"
In some cases, if $line contains commands that are meant to run in background, eg command 2>&1 & they will not start in background, and will run in the current script context.
Any ideea why?
if all your commands are inside "commands.txt", essentially, you can call it a shell script. That's why you can either source it, or run it like normal, ie chmod u+x , then you can execute it using sh commands.txt
I don't have anything to add to ghostdog74's answer about the right way to do this, but I can cover why it's failing: The shell parses I/O redirections, backgrounding, and a bunch of other things before it does variable expansion, so by the time $line is replaced by command 2>&1 & it's too late to recognize 2>&1 and & as anything other than parameters to command.
You could improve this by using eval "$line" but even there you'll run into problems with multiline commands (e.g. while loops, if blocks, etc). The source and sh approaches don't have this problem.