Echo command output "-n" in a file from Makefile - makefile

I have a Makefile that has this kind of command:
browserify:
rm -rf ./dist
mkdir dist
# Browserify
echo -n "/* ${NPM_PACKAGE} ${NPM_VERSION} ${GITHUB_PROJ} */" \
> dist/pica.js
So when I do make browserify it should output the comment to the top of the file without \n. But for some reasons... the output looks like this
-n /* package 0.0.0 */
...more things...
I'm using zsh on osx.

It doesn't matter what shell you are using. Make will always use /bin/sh as the shell it invokes (unless you specifically set the SHELL make variable to something else). Think what a disaster it would be if make used whatever shell the user was using to invoke recipes!
On many GNU/Linux systems, /bin/sh is actually a link to bash. On other GNU/Linux systems, /bin/sh is a link to dash which is a small, POSIX-standard shell without all the extensions bash uses (dash is good for running portable shell scripts fast, but not good for a user's interactive shell as it's missing too many expected features). On non-GNU-based systems (like OSX) /bin/sh might be ksh or something else even.
There is no portable, standard way to invoke echo in such a way that it doesn't print the trailing newline. There is an echo program, and different ones work differently. Many shells, including bash and zsh also have an echo built-in to the shell:
$ type -a echo
echo is a shell builtin
echo is /bin/echo
and these versions of echo also work differently than the program echo. Some versions have no way to suppress newlines. Some use the -n flag. Some use \c at the end to suppress printing the newline. Some support a combination of them.
The short answer is that if you want to print a line in the shell without a newline in a portable and reliable way, you should use the printf program to do it, not echo:
browserify:
rm -rf ./dist
mkdir dist
# Browserify
printf %s "/* ${NPM_PACKAGE} ${NPM_VERSION} ${GITHUB_PROJ} */" \
> dist/pica.js

Related

Disable functions/aliases in a sourced script

I know I can run an "original" command (not alias) using either \ or "":
\ls
"ls"
This doesn't work for functions though. Also it requires me to use that syntax every time.
Is it possible in a sourced script to disable all functions/aliases from the parent process (one which runs my script)? I.e. if a user in their terminal has some aliases functions defined I want them disabled in my script (but of course I still want to be able to define and use aliases/functions of my own).
Types of Commands in Bash
Bash knows different types of commands which can shadow each other. The precedence of these types is:
aliases
can be defined by the user using alias cmd=...
functions
can be defined by the user using cmd() { ... }
built-ins
are directly implement in bash and cannot be altered. help and enable list all built-ins.
Executable files in $PATH
Meaning if you type cmd arg1 arg2 ... you use the alias cmd if it is defined, otherwise you use the function cmd if it is defined, otherwise you use the built-in cmd if it is built-in, otherwise you use the first executable cmd from the directories in $PATH if there is one, otherwise you end up with the error -bash: cmd command not found.
Which of these cases applies for cmd can be checked using type -a cmd.
Manual Precedence Control
Bash allows you to influence which type to pick using quoting and the built-ins command and builtin.
\cmd
suppresses aliases
uses functions, built-ins, executables
command cmd
suppresses aliases and functions
uses built-ins and executables
builtin cmd
supresses aliases, functions, and executables
uses only built-ins
enable -n cmd
disables the built-in cmd completely, such that afterwards only
aliases, functions, and executables are used
env cmd
not a bash built-in, therefore it doesn't really suppress anything but
uses only executables
Examples
Shadowing is perfectly normal. For instance, bash has its own built-in echo, but your system also has /bin/echo. Both implementations may differ. For instance, my echo from bash 5 supports \uXXXX but my echo from GNU coreutils 8.3 does not. The possibility of such differences becomes even more clear if you add your own implementations using aliases and functions. Here's an example in an interactive bash session ($ is the prompt):
$ echo() { printf "function echo: %s\n" "$*"; }
$ alias echo='printf "alias echo: %s %s %s\n"'
$ type -a echo
echo is aliased to `printf "alias echo: %s %s %s\n"'
echo is a function
echo ()
{
printf "function echo: %s\n" "$*"
}
echo is a shell builtin
echo is /bin/echo
$ echo -e '\u2261'
alias echo: -e \u2261
$ \echo -e '\u2261'
function echo: -e \u2261
# use the built-in (or executable file if there was no such built-in)
$ command echo -e '\u2261'
≡
$ builtin echo -e '\u2261'
≡
# use the executable /bin/echo
$ env echo -e '\u2261'
\u2261
$ enable -n echo
# use the executable /bin/echo (`command` is needed to skip the alias and function)
$ command echo -e '\u2261'
\u2261
Answering your Question
Unfortunately I'm not aware of something like enable to permanently disable alias and function lookup. You could try some hacks like backing up all aliases and functions, doing unset -f and unalias on them, and restoring them at the end. However, unset may fail for readonly functions. The better way would be to use bash -c '... functions and aliases have no effect here ...' for the parts where you don't really need the benefits of source. For the other parts, prefix everything with command.
Please note: The caller who sources your script may even disable or shadow command, builtin, and so on -- therefore you can never be sure that you are actually using the commands you expected. Even writing /usr/bin/env executable or /path/to/the/executable does not help as a function can have the name and $PATH or the file system can be altered.
However, that shouldn't be your concern. The one who sources your script should be responsible for providing the correct environment.
Edit: this answer might no longer be relevant since you edited the question to clarify that the script is being sourced, not being executed in a subshell.
This happens by default. Proof:
$ function x() { echo 'hi'; }
$ x
hi
$ bash
# We are now in a subshell.
$ x
bash: x: command not found
Functions are often defined in one of the shell's startup files: .bashrc, .profile or .bash_profile. Which of these are sourced depends on whether the shell is a login shell and/or an interactive shell. A shell that invoked to execute a shell script is neither a login shell nor an interactive shell, and in this case none of those files are sourced.
EDIT: I should read more carefully, as you don't want to source a script, but be sourced, the following is for the other way around:
Functions
If you source your parent script at the beginning, you can just loop through the defined functions and unset them.
declare -F will list all defined functions but in the format declare -f functioname, so you have to get only the name:
IFS=$'\n'
for f in $(declare -F|cut -d ' ' -f 3); do
unset -f $f
done
Aliases
Alias should not be sourced in as i remember, but if they are there you can do
unalias -a
to unset them all.

bash script yields a different result when sourced

Could you help me, why this script works when sourced (or even directly on console) and does not work on a script?
I have checked and in any case I'm using the same bash in /bin/ and always 4.4.19(1)-release (checked with $BASH_VERSION).
Moreover I tried removing shebang but nothing changes.
#!/bin/bash
fname=c8_m81l_55.fit
bname=${fname%%+(_)+([0-9]).fit}
echo $bname
GIving these results:
test:~$ ./test.sh
c8_m81l_55.fit
test:~$ . ./test.sh
c8_m81l
Bash does not recognize +(pattern) syntax unless extglobs are enabled, and they are disabled by default. Apparently your bash setup enables them in interactive sessions; that's why your script works only when sourced in an interactive shell.
To fix that, either enable extglobs within the script by this command:
shopt -s extglob
Or use an alternative that works irrespective of shell's interactiveness:
bname=$(sed 's/__*[0-9][0-9]*\.fit$//' <<< $fname)
# with GNU sed it'd look like:
bname=$(sed -E 's/_+[0-9]+\.fit$//' <<< $fname)

Shell wildcard/glob for optional string in a makefile

I'm trying to "optimize" my 'clean' target in multi-platform Makefiles so I was looking for a way to remove executable files with or without the Windows-extension .exe.
Of course, you could do
rm file file.exe
but I was looking for something like
rm file(.exe)?
I also tried
rm file{,.exe}
which doesn't work either.
I was surprised to see that what I tried did not work, so I'm mostly posting this to learn more about globing, as the version with the two explicit filenames works fine.
Make executes commands with /bin/sh by default, which has limited globbing support.
$ cat Makefile
test:
echo foo{bar,baz}
$ make
echo foo{bar,baz}
foo{bar,baz}
If you want fancy features like curly braces to work you'll need to switch the shell by setting SHELL.
$ cat Makefile
SHELL = /bin/bash
test:
echo foo{bar,baz}
$ make
echo foo{bar,baz}
foobar foobaz
I wouldn't necessarily advise doing so as it makes your Makefile less portable.
For what it's worth, GNU Automake's strategy is to set an EXEEXT variable based on the platform. Then the clean rule is:
rm -f file$(EXEEXT)

csh doesn't recognize command with command line options beginning with --

I have an rsync command in my csh script like this:
#! /bin/csh -f
set source_dir = "blahDir/blahBlahDir"
set dest_dir = "foo/anotherFoo"
rsync -av --exclude=*.csv ${source_dir} ${dest_dir}
When I run this I get the following error:
rsync: No match.
If I remove the --exclude option it works. I wrote the equivalent script in bash and that works as expected
#/bin/bash -f
source_dir="blahDir/blahBlahDir"
dest_dir="foo/anotherFoo"
rsync -av --exclude=*.csv ${source_dir} ${dest_dir}
The problem is that this has to be done in csh only. Any ideas on how I can get his to work?
It's because csh is trying to expand --exclude=*.csv into a filename, and complaining because it cannot find a file matching that pattern.
You can get around this by enclosing the option in quotes:
rsynv -rv '--exclude=*.csv' ...
or escaping the asterisk:
rsynv -rv --exclude=\*.csv ...
This is a consequence of the way csh and bash differ in their default treatment of arguments with wildcards that don't match a file. csh will complain while bash will simply leave it alone.
You may think bash has chosen the better way but that's not necessarily so, as shown in the following transcript where you have a file matching the argument:
pax> touch -- '--file=xyzzy.csv' ; ls -- *.csv
--file=xyzzy.csv
pax> echo --file=*.csv
--file=xyzzy.csv
You can see there that the bash shell expands the argument rather than giving it to the program as is. Both sides have their pros and cons.

How do I change Korn(ksh) version dynamically based on platform?

I wanted to use the /usr/bin/ksh93 interpreter on AIX and Linux wherever possible but switch to /usr/bin/ksh where it's not applicable like Mac OS X and wanted the script to be universally compatible in unix. I don't think there is any fallback mechanism in shebang
Since ksh and sh have some syntax in common, you can prefix the start of the
script with a test for ksh or ksh93 in the PATH and rerun the script with
the right interpreter. Replace the #! with the pathname to sh. (Hopefully
it is the same on both machines, or you are back where you started. You can
still try #!/usr/bin/env sh if your env will find the path for you). Add:
#!/bin/sh
if [ "$DONEIT" != true ]
then export DONEIT=true # avoid recursion
if command -v ksh > /dev/null 2>&1
then exec ksh $0 "$#"
else exec ksh93 $0 "$#"
fi
fi
... rest of your script ...
Note: command -v is the POSIX way for finding a command's path.
(Often in these situations, at the installation of a package a script goes
through the #! files and updates the interpreter path to that needed by the
target machine).
Alternatively, you could replace the #! line by any fixed path you control, eg #!/home/user/myksh, and link that file to the right ksh.
You can make a symbolic links.
if [ -f /usr/bin/ksh93 ]; then
ln -s /usr/bin/ksh93 /usr/bin/localksh
else
ln -s /usr/bin/ksh /usr/bin/localksh
fi
The shebang will be #!/usr/bin/localksh.
I would prefer using a normal shebang #!/bin/ksh, but when that one already exists and is the wrong version you will be stuck.

Resources