why are the prerequisites not set as expected? - makefile

I'm trying to write this makefile where the prerequisites for a target should be the output of a shell command. here's what I have
foo: ($shell find mydir -name "*.ext")
# command goes here
where mydir is a directory under the same directory where the makefile itself. however, executing make foo executes the recipes even if none of the *.ext files were modified (i.e., it always executes the recipes acting as if there are prerequisites set).
I'm sure that the find command outputs the expected results.
any idea what might be going wrong here and how to get it to behave expectedly?

A little test to see if it is working. Here the Makefile:
foo: $(shell find mydir -name "*.ext")
cat $^ > $#
And the file tree:
.
├── Makefile
└── mydir
├── bar.ext
└── foo.ext
Then run the Makefile:
$ make
cat mydir/foo.ext mydir/bar.ext > foo
$ make
make: `foo' is up to date.
$ touch mydir/foo.ext
$ make
cat mydir/foo.ext mydir/bar.ext > foo
So it's working well. Just be sure that your foo file is created by the build rule.

Related

How to create a directory within a directory using shell scripting?

So after an uncomfortable number of hours of trying to do this, this is what I'm trying to do:
Write a bash shell script called direct.sh. This script will take an arbitrary number of command line arguments. Your script should create directories starting with the first argument, then the second directory inside the first one, and then the next one inside the second one, and so on.
For e.g. direct.sh dir1 dir2 dir3
should first create dir1, then create dir1/dir2, then finally create dir1/dir2/dir3
This is what I have done after 20 hours.
#!/bin/bash
for i in $#
do
mkdir -p $1/$i
done
I know its wrong. Please help.
This can be done in a single step, since mkdir -p will create all needed parent directories on the way (that's what the -p option does).
#!/bin/bash
IFS=/
mkdir -p "$*"
Explanation: $* expands to all of the script's arguments, spliced together into a single string, separated by the first character of IFS. That's normally a space character, but here it's set to "/" instead. The double-quotes around it prevent unexpected word-splitting or wildcard expansion.
So, if you run direct.sh dir1 dir2 dir3, it executes mkdir -p "dir1/dir2/dir3", which creates all 3 directory levels (or at least, those that don't already exist).
BTW, in general you should set IFS back to normal after changing it like this. But since there's nothing afterward in the script that might get messed up, and changing it in a script doesn't affect the parent process (the shell you used to run the script), there's no need to set it back in this case.
You are close, but you need to create the directories one-at-a-time and then change into the newly created directory before creating the next. That way with each argument you take you create one-level of directory, e.g.
#!/bin/bash
for i in "$#"; do
mkdir -p "$i" && cd "$i"
done
(note: by using the compound command joined with &&, you only change to the new directory if mkdir -p succeeds)
Another way to write it would be:
for i in "$#"; do
mkdir -p "$i"
if [ -d "$i" ]; then
cd "$i"
fi
done
You ALWAYS quote your variables. That way iterating over the arguments if they are:
bash yourscript.sh dir1 "dir1 a" dir2
You will actually create your three directories dir1, dir1 a and dir2. If you FAIL to QUOTE, you would create dir1, dir1, a and dir2 (4-directories)
Example Use/Results
$ bash myscript.sh my dog has fleas
Checking:
$ tree my
my
└── dog
└── has
└── fleas
Let me know if you have questions.
a sed way of doing.
with sed we are just replacing the spaces with "/" which would do the trick
cat hi.sh
mkdir -p `echo $#|sed -e 's/\ /\//g'`
execute the script with arguments
./hi.sh 1 2 3 4 5
listing the created directories
tree
.
├── 1
│   └── 2
│   └── 3
│   └── 4
│   └── 5
├── hi.env
└── hi.sh

simple loop over files in some directory makefile

I can easily print all the files inside some directory from bash:
$ cat go.sh
BASEDIR=~/Downloads
MYDIR=${BASEDIR}/ddd
for f in $(ls ${MYDIR}); do echo $f; done
$ ./go.sh
m.txt
d.txt
When I try to do a similar thing from makefile it doesn't work well:
$ cat makefile
BASEDIR = ${HOME}/Downloads
MYDIR = ${BASEDIR}/ddd
all:
for f in $(ls ${MYDIR}); do echo ${f}; done
$ make
for f in ; do echo ; done
And here is another trial that doesn't work:
$ cat makefile
BASEDIR = ${HOME}/Downloads
MYDIR = ${BASEDIR}/ddd
all:
for f in $(shell ls ${MYDIR}); do echo ${f}; done
$ make
for f in d.txt m.txt; do echo ; done
Maybe you can do it purely Makefile way?
MYDIR = .
list: $(MYDIR)/*
#echo $^
You can still run command from Makefile like this
MYDIR = .
list: $(MYDIR)/*
for file in $^ ; do \
echo "Hello" $${file} ; \
done
If I were you, I'd rather not mix Makefile and bash loops based on $(shell ...). I'd rather pass dir name to some script and run loop there - inside script.
Also almost "true way" from documentation
TEMPLATES_DIR = ./somedir
list:
$(foreach file, $(wildcard $(TEMPLATES_DIR)/*), echo $(file);)
Here is the edited answer based on #Oo.oO:
$ cat makefile
BASEDIR = ${HOME}/Downloads
MYDIR = ${BASEDIR}/ddd
all:
#for f in $(shell ls ${MYDIR}); do echo $${f}; done
$ make
d.txt
m.txt
There is a little problem with #Oo.oO's answer.
If there is any file/folder has the same name with a target in makefile, and that target has some prerequisites, and you want to loop through that folder, you will get that target recipe being executed.
For example: if you have a folder named build, and you have a rule like:
build: clean server client
clean:
#echo project cleaned!
server:
#echo server built!
client:
#echo client built!
To loop through the folder contains that special build folder, let's says you have the following rules:
MYDIR = .
ls: $(MYDIR)/*
#echo $^
The result will be:
$ make ls
project cleaned!
server built!
client built!
build Makefile
I would suggest to use #Mike Pylypyshyn's solution. According to the make documentation, the foreach function is more suitable in this case.

Check if sub directories are present in ./work when it is created inside makefile?

In my makefile, I want to check if sub directories are present in a given folder.
This is how I am doing it now..
setup:
mkdir -p ./work
DIR=$(shell find ./work -maxdepth 0 -type d -print)
check:
if [-z $(DIR) ]; then \
echo "null" ; \
else \
echo "present" ; \
fi;
I cannot create the ./work outside the makefile. So the problem is that since ./work is created inside the makefile, it gives me an error/warning that ./work is not present before actually executing the script.
Is there a way to check if sub directories are present in ./work when it is created inside makefile?
One possibility, which also is supportive of the make principle to build/create only things that your target is depending on, are order-only prerequisites. Now that you have heard this term order-only I want you from now on to mentally replace it with the much better fitting term "existence-only" (which is completely unofficial because I made it up) to gain an easier understanding. order-only prerequisites are written in the prerequisite list to the right side of a | pipe symbol and their meaning is: the target of this rule depends on the existence of this prerequisite but not on its timestamp. This is exactly what we want from a rule that needs to place something inside of a directory: if the directory exists, then we don't care about its date (mainly because in Unix directories get a new timestamp when a new file is created there) and we carry on with the rest of the targets dependencies. If however the directory doesn't exist, execute its rule first, which hopefully creates the directory:
.PHONY: all
all: foo
#cat subdir/subsubdir/subsubsubdir/foo.txt
subdir/subsubdir/subsubsubdir/foo.txt: | subdir/subsubdir/subsubsubdir
#echo This is file foo.txt > subdir/subsubdir/subsubsubdir/foo.txt
subdir:
mkdir subdir
subdir/subsubdir: | subdir
mkdir subdir/subsubdir
subdir/subsubdir/subsubsubdir: | subdir/subsubdir
mkdir subdir/subsubdir/subsubsubdir

Makefile loop results from ls command

I have a list of text files, which when executing the following command:
ls *.txt
Will result in something like:
foo.txt bar.txt baz.txt
Now if I want to have the output be something like:
File: foo.txt
File: bar.txt
File: baz.txt
How would I achieve that in a Makefile?
I've been trying:
txtfiles = $$(ls *.txt)
list: $(txtfiles)
$(txtfiles):
echo "File:" $#
But when running this with make list, it results with:
File: $
File: $
I was thinking of trying to achieve this using placeholders % but wasn't sure how to do that.
Note: instead of $$(ls *.txt) I'm guessing I could use $(wildcard *.txt) ?
Note: instead of $$(ls *.txt) I'm guessing I could use $(wildcard *.txt)?
You have to.
Also add .PHONY: $(txtfiles) before $(txtfiles): rule to make an explicit request.
OK so I figured this out:
my_list = $(addsuffix .dep, $(wildcard *.txt))
print_list: $(my_list)
%.dep: %
#echo "Text File:" $<
Run it with make print_list to get back the expected result.

bash globbing (globstar) pattern errors if top level directory empty with "No such file or directory"

I have a folder structure as such:
a/
b/
test.js
c/
another_test.js
When I want to find all these files, I tried the globstar approach:
ls a/{,**/}*.js
However, this command errors (but still outputs files) because there is no a/*.jsx file:
$ ls a/{,**/}*.jsx
ls: a/*.jsx: No such file or directory
a/b/test.js
I want to use this specific glob because in the future, at some point, a/test.js could exist.
Is there a glob pattern that will find all .js files in a/ recursively and not error?
I looked at some of the options in this question but couldn't find anything that doesn't error and lists all files.
With bash4 and above, just use:
ls dir/**/*.js
With previous bash versions, such as 3.2 shipped with osx, you can use find:
find dir -name '*.js'
Given:
% tree .
.
└── a
├── b
│   └── test.js
└── c
└── another_test.js
You can use the pattern a/**/*.js on either zsh or Bash4+ with shopt -s globstar set:
zsh:
% ls -1 a/**/*.js
a/b/test.js
a/c/another_test.js
Bash 5.1:
$ ls -1 a/**/*.js
a/b/test.js
a/c/another_test.js

Resources