Make `shell` command returns outdated symlink target - bash

If I create a symlink pointing to a (non-existant) target, old.
ln -sf "old" /tmp/symlink
Then run the following Makefile:
SHELL := /bin/bash
all:
#ln -sf "new" /tmp/symlink
#ls -l /tmp/symlink
#echo "$(shell ls -l /tmp/symlink)"
It outputs two different targets for the symlink:
lrwxrwxrwx ... /tmp/symlink -> new
lrwxrwxrwx ... /tmp/symlink -> old
Why does the shell command return an outdated target for the symlink?

Related

Why `ln -s scripts/hooks/prepare-commit-msg .git/hooks/` does not work?

Why ln -s scripts/hooks/prepare-commit-msg .git/hooks/ does not create a proper working link? scripts/hooks/prepare-commit-msg exists.
Let's look at the symlink you created with that ln command:
$ ls -l .git/hooks/prepare-commit-msg
lrwxrwxrwx. 1 lars lars 32 Mar 3 07:35 .git/hooks/prepare-commit-msg -> scripts/hooks/prepare-commit-msg
There is no path scripts/hooks/prepare-commit-msg available from inside the .git/hooks directory. For that to work, you need to provide an appropriate relative path:
$ ln -s ../../scripts/hooks/prepare-commit-msg .git/hooks

extra links from foreach in Makefile

I'm on a mac using GNU Make to manage my dotfiles. There's a directory with my emacs config files and a corresponding target in the Makefile:
all: _emacs
.PHONY: all list $(MAKECMDGOALS)
....
EMACS_SOURCE_DIR := $(abspath ./emacs)
EMACS_TARGET_DIR := $(abspath $(HOME)/.emacs.d)
EMACS_CONFIG_FILES := $(wildcard $(EMACS_SOURCE_DIR)/*.el)
_emacs: | $(EMACS_TARGET_DIR)
#echo $(call message,"Setting up config files for emacs")
$(foreach file, \
$(EMACS_CONFIG_FILES), \
ln -sf $(file) $(addsuffix /, $(EMACS_TARGET_DIR)))
$(EMACS_TARGET_DIR):
#echo "Creating directory $(EMACS_TARGET_DIR)"
#mkdir -p $#
When I run make _emacs it creates some extra links:
.emacs.d -> /Users/xxx/.emacs.d/
-sf -> -sf
early-init.el -> /Users/xxx/Projects/bootstrap/emacs/early-init.el
init-org.el -> /Users/xxx/Projects/bootstrap/emacs/init-org.el
init-pkgs.el -> /Users/xxx/Projects/bootstrap/emacs/init-pkgs.el
init.el -> /Users/xxx/Projects/bootstrap/emacs/init.el
ln -> ln
I'm struggling to understand what exactly is happening and how to avoid having the first two and the last soft links created.
In general it's a bad idea to try to construct a complex shell command using make functions inside a recipe. You should simply use shell constructs: for example use the shell for loop, not the make foreach loop.
Let's see what your recipe does:
_emacs: | $(EMACS_TARGET_DIR)
$(foreach file, \
$(EMACS_CONFIG_FILES), \
ln -sf $(file) $(addsuffix /, $(EMACS_TARGET_DIR)))
Make functions manipulate text. They don't know anything about commands, shell syntax, etc. Before make invokes the shell it first expands the script. What will this result in? This:
ln -sf .../emacs/early-init.el /Users/xxx/.emacs.d/ ln -sf .../emacs/init-org.el /Users/xxx/.emacs.d/ ln -sf ...
Maybe now you can see the problem.
If you wanted to do this using make's foreach you have to add a shell delimiter so the shell knows where one command ends and the next begins; something like:
$(foreach file, \
$(EMACS_CONFIG_FILES), \
ln -sf $(file) $(addsuffix /, $(EMACS_TARGET_DIR)) ; )
(note the ; at the end). Now when make expands it will look like this:
ln -sf .../emacs/early-init.el /Users/xxx/.emacs.d/ ; ln -sf .../emacs/init-org.el /Users/xxx/.emacs.d/ ; ln -sf ...
If I were you, I'd instead use a shell loop. It's just much simpler to understand:
for file in $(EMACS_CONFIG_FILES); do \
ln -sf $$file $(addsuffix /, $(EMACS_TARGET_DIR)) ; \
done
But, for what you want to do you don't need a loop at all; you can link multiple files into the same directory with one command:
ln -sf $(EMACS_CONFIG_FILES) $(EMACS_TARGET_DIR)
(I'm not sure what the addsuffix was needed for, or why you couldn't just write it as $(EMACS_TARGET_DIR)/ without the addsuffix)

Linux symbolic links how to make target file in currently directory an automatic absolute path?

Here is the story:
cd ~
mkdir bin
export PATH=$PATH:bin
mkdir -p projects
cd projects
echo 'hello world' > hello.sh
chmod +x hello.sh
ln -s hello.sh ~/bin/hello
hello
output:
-bash: hello: command not found
How I changed it:
ln -s hello.sh ~/bin
hello.sh
The output is more weird:
-bash: /home/qht/bin/hello.sh: Too many levels of symbolic links
I ls it to see what happened:
ls -l ~/bin/hello.sh
/home/qht/bin/hello.sh -> hello.sh
I figure it out, hello.sh reference itself. And hello before reference hello.sh which doesn't exist.
I fix it by:
ln -sf $PWD/hello.sh ~/bin/hello
ls ~/bin/hello
/home/qht/bin/hello -> /home/qht/projects/hello.sh
and it works, I also man ln to see if there is a convenient option to do that, this is what I found:
ln -sfr hello.sh ~/bin/hello
ls -l ~/bin/hello
/home/qht/bin/hello -> ../projects/hello.sh
And it works, the -r option did the work.
But I'm curious, if ln -r can automatically write the relative path data into symbolic links, Why doesn't there an option maybe -a to do the absolute path work.
Or, is relative path for links is more practical than absolute path?
Try this:
cd ~
mkdir bin
export PATH=$PATH:~/bin # Need absolute path to bin
mkdir -p projects
cd projects
echo 'echo "hello world"' > hello.sh # If the script is just hello world
# this will become an infinite loop
chmod +x hello.sh
ln -s "$PWD/hello.sh" ~/bin/hello # the symbolic link in this case
# needs to be a absolute path
hello

/usr/bin/env: ln: Too many levels of symbolic links

This problem is killing me and I feel like I've tried everything.
First off, the problem started happening when upgrading to Capistrano 3. Capistrano now utilizes /usr/bin/env before every command when deploying, to make sure the environment setup is correct.
When Capistrano goes to create symlinks to the necessary shared directory and respective files, it attempts commands like:
/usr/bin/env ln -s /full/path /different/full/path
...and then it errors out:
/usr/bin/env: ln: Too many levels of symbolic links
I realize it's not Capistrano's fault, so I began troubleshooting by ssh'ing to my server and trying the same command, and I receive the same error (which at least is good for consistency). I then try the same command without /usr/bin/env:
ln -s /full/path /different/full/path
And it works!!!! Maybe you can see the real solution that I can't?
here is the output of just the /usr/bin/env command:
rvm_bin_path=/home/deployer/.rvm/bin
GEM_HOME=/home/deployer/.rvm/gems/ruby-1.9.3-p392
TERM=xterm-256color
SHELL=/bin/bash
IRBRC=/home/deployer/.rvm/rubies/ruby-1.9.3-p392/.irbrc
SSH_CLIENT=...
OLDPWD=/home/deployer/Sites/example.com
MY_RUBY_HOME=/home/deployer/.rvm/rubies/ruby-1.9.3-p392
SSH_TTY=/dev/pts/0
USER=deployer
LS_COLORS= .....
_system_type=Linux
rvm_path=/home/deployer/.rvm
SSH_AUTH_SOCK=....
rvm_prefix=/home/deployer
MAIL=/var/mail/deployer
PATH=/home/deployer/.rvm/gems/ruby-1.9.3-p392/bin:/home/deployer/.rvm/gems/ruby-1.9.3-p392#global/bin:/home/deployer/.rvm/rubies/ruby-1.9.3-p392/bin:/home/deployer/.rvm/bin:/opt/rubyee/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/home/deployer/.rvm/bin
PWD=/home/deployer/Sites
LANG=en_US.UTF-8
_system_arch=i386
_system_version=12.04
rvm_version=1.26.4 (latest)
SHLVL=1
HOME=/home/deployer
LOGNAME=deployer
GEM_PATH=/home/deployer/.rvm/gems/ruby-1.9.3-p392:/home/deployer/.rvm/gems/ruby-1.9.3-p392#global
SSH_CONNECTION=....
LESSOPEN=| /usr/bin/lesspipe %s
LESSCLOSE=/usr/bin/lesspipe %s %s
RUBY_VERSION=ruby-1.9.3-p392
_system_name=Ubuntu
_=/usr/bin/env
I have also tried commands like the following, to find potential symlink loops:
find . -maxdepth 20 -type l -exec ls -ld {} +
But is not producing correct results:
lrwxrwxrwx 1 deployer deployer ...
You might not being using the same ln utility.
When invoking it directly from the interactive shell, ln might be overridden e.g. by an alias or by some shell function ln() {...;}.
This does not happen when /usr/bin/env tries to do that (AFAIK it looks for ln in PATH). I suspect that the ln it finds has issues, so you are getting this error.
This is an example scenario that might be similar to your case:
# start from an empty directory
$ ls -l
total 0
# create a problematic `ln` in the current directory
$ ln -s ln ln
$ ls -l
total 0
lrwxrwxrwx 1 me me 2 Jan 7 20:28 ln -> ln
# have an alias for the "real" ln
$ alias ln=/bin/ln
# mess up PATH
$ PATH="$PWD"
Now let's try the two alternatives, /usr/bin/env goes first:
$ /usr/bin/env ln -s /some/path /tmp/path
/usr/bin/env: ln: Too many levels of symbolic links
Then plain ln (remember that we aliased it):
$ ln -s /some/path /tmp/path
$ echo $?
0
$ /bin/ls -l /tmp/path
lrwxrwxrwx 1 me me 10 Jan 7 20:31 /tmp/path -> /some/path
So my suggestion is: look at issues with ln, e.g. by finding all different alternatives that might be visible. In bash you might run this:
$ type -a ln
Try this to find symlink loops:
find . -follow -printf ""
if you are using docker so you should install ruby in your case
docker run ruby
source
https://github.com/docker/for-win/issues/5763#issuecomment-585749243

Suppress make rule error output

I have an rule that creates a directory
bin:
-mkdir $#
However after the first time the directory has been generated, I receive this output:
mkdir bin
mkdir: cannot create directory `bin': File exists
make: [bin] Error 1 (ignored)
Is there some way I can only run the rule if the directory doesn't exist, or suppress the output when the directory already exists?
Another way to suppress the make: error ... (ignored) output is to append || true to a command that could fail. Example with grep that checks for errors in a LaTeX log file:
undefined:
#grep -i undefined *.log || true
Without the || true, make complains when grep fails to find any matches.
This works for all commands, not just mkdir; that's why I added this answer.
The error is ignored already by the leading '-' on the command line. If you really want to lose the error messages from mkdir, use I/O redirection:
bin:
-mkdir bin 2> /dev/null
You will still get the 'ignored' warning from make, though, so it might be better to use the option to mkdir that doesn't cause it to fail when the target already exists, which is the -p option:
MKDIR_P = mkdir -p
bin:
${MKDIR_P} $#
The -p option actually creates all the directories that are missing on the given paths, so it can generate a a number of directories in one invocation, but a side-effect is that it does not generate an error for already existing directories. This does assume a POSIX-ish implementation of mkdir; older machines may not support it (though it has been standard for a long time now).
The traditional way to handle directory creation is to use a stamp file that is depended on and creates the dir as a side effect. Remove the stamp file when making distclean or whatever your "really clean" target is:
bin/.dirstamp:
mkdir -p $(DIRS)
touch $#
bin/foo: bin/.dirstamp
$(MKFOO) -o $#
distclean:
rm -rf bin
The reason for this is as follows: whenever a file in bin is created/removed, the mtime of the containing directory is updated. If a target depends on bin, then the next time make runs, it will then recreate files that it doesn't need to.
Well I ended up with this construct, maybe someone will find it useful or can comment on it:
BINDIR = .
TMPDIR = tmp
OUTDIRS = $(BINDIR) $(TMPDIR)
$(OUTDIRS):
#test -d $# || mkdir $#
You rule should not be executed unless its target does not exists or is out of date because of its dependencies. In other words, you should never encounter this error.
[Example Added]
[max#truth tmp]$ ls -la
total 20
drwxr-xr-x. 2 max users 4096 Aug 14 21:11 .
drwx------. 80 max max 4096 Aug 14 18:25 ..
-rw-rw-r-- 1 max max 38 Aug 14 21:11 Makefile
[max#truth tmp]$ cat Makefile
.PHONY: all
all: bin
bin:
mkdir $#
[max#truth tmp]$ make
mkdir bin
[max#truth tmp]$ ls -la
total 24
drwxr-xr-x. 3 max users 4096 Aug 14 21:11 .
drwx------. 80 max max 4096 Aug 14 18:25 ..
drwxrwxr-x 2 max max 4096 Aug 14 21:11 bin
-rw-rw-r-- 1 max max 38 Aug 14 21:11 Makefile
[max#truth tmp]$ make
make: Nothing to be done for `all'.
[max#truth tmp]$ make
make: Nothing to be done for `all'.
Does your folder depend on anything? Is your folder a phony target?
Make assumes the first target is the default target. If that is your complete makefile, then it must be always trying to remake the default target, which is bin. Insert the following lines to the top of your makefile:
all: bin
.PHONY: all

Resources