I have a makefile that runs some custom tools. One of those tools sometimes spits out an extra file. That file must be used in a subsequent recipe (if it exists). I can't assume it exists in the dependencies, but I must use add it to the subsequent recipe command if it is there. Here's how I currently handle it:
final_recipe:
[ ! -f "maybe.file" ] || tool maybe.file ...
[ -f "maybe.file" ] || tool ...
This is ugly. Any suggestions for a bash executable line where bash can evaluate whether the file exists and embed it if it does? Something more like:
final_recipe
tool ([ -f "maybe.file"] ? maybe.file) ...
This may achieve what you wanted :
final_recipe:
tool $$(test -f maybe.file && echo maybe.file) ...
Updated upon MadScientist's comment
Related
We were assigned a project that works with various file. The code in question is this:
if [[ -f $first_arg ]]; then
open_file $first_arg
elif [[ -d $first_arg ]]; then
search_directory $first_arg
.....
It works fine with just regular files, but it comes into the second condition if I run the script like this (with the ~/.) :
./script01.sh ~/.config
So I'm wondering what goes on when bash checks -f and -d, what is considered a directory or file and what is not anymore.
~/.config is quite commonly a directory as Joe has suggested in the comments.
As to what goes on, bash apparently calls stat(2) on the file in question which returns a corresponding structure including st_mode field. Details for that are in inode(7):
The stat.st_mode field (for statx(2), the statx.stx_mode field) con‐
tains the file type and mode.
Namely:
The following mask values are defined for the file type:
...
S_IFREG 0100000 regular file
...
S_IFDIR 0040000 directory
All that is left is to check which bits are set.
I use to run libreoffice headless for executing some macros from a bash script:
soffice --headless "$year_to_process"/"$initials".ods macro:///Standard.C69.MergeSaveClose
Before I do that, I need to check if the macro file c69.xba is present in the directory $HOME/.config/libreoffice/4/user/basic/Standard/.
The problem is that the digit '4' above seems to me to be version dependant. Another problem is that that digit doesn't match exactly the major version number returned by libreoffice --version, which outputs:
LibreOffice 5.4.5.1 40m0(Build:1)
What I have coded so far is:
# check the macro .config/libreoffice/*/user/basic/Standard/c69.xba is present
[ -f $HOME/.config/libreoffice/*/user/basic/Standard/c69.xba ] || {
echo "$cmd: missing file basic/Standard/c69.xba"
exit 1
}
using a '*' to match any version, but when a second directory will appear, as .config/libreoffice/{4,5}/user/basic/Standard, my code will no longer work.
My question is: how can I get (from the command line) the correct path to the directory containing the macros used by the current version of libreoffice, without using a '*'?
Starting from the comment above of Aserre about the arbitrary character of the path to the configuration of LibreOffice, I chose to select the most recently created profile of LibreOffice, using the -t option of the ls command. So I found next solution to work:
# check the macro .config/libreoffice/*/user/basic/Standard/c69.xba is present
path=$(ls -dt "$HOME"/.config/libreoffice/* | head -n1) # take only first line of output of ls -dt
[ -f "$path/user/basic/Standard/c69.xba" ] || {
echo "$cmd: missing file basic/Standard/c69.xba"
exit 1
}
I'm making a makefile and I'm working on a target with no dependencies that checks to see if a file named README exists in the current directory and if it does, read it using less or else exit quitely: give no errors whatsoever--the command to run the target should print nothing to console if this is the case.
I've tried a couple different ways but nothing really seems to work (PAGER is simply a var equal to less):
read :
ifneq ("$(wildcard README)","")
-#$(PAGER) README
endif
And also with this code
read :
-#for file in *;\
do \
if [ ${file} == "README" ]; then\
$(PAGER) "README" ;\
fi;\
done
With the first chunk I keep getting an error akin to /bin/sh: Syntax error: word unexpected (expecting ")") and for the life of me I just don't get what it's saying. I certainly don't think there's a syntax error, perhaps I'm misusing make.
For the latter code I get an unexpected operator error for the ==.
I've also tried simpler things like these two single liner solutions but get similar errors:
-#test -a README && less README
-#[ -a README ] && less README
Any help would be greatly appreciated.
EDIT: I was digging further and saw something promising and explicitly setting my shell to /bin/bash (SHELL := /bin/bash) yet no dice.
read:
#if [ -e README ]; then less README; fi
You should try to extract your shell code in separate *.sh files so that you can specify them as commands in your makefile (and make sure that the *.sh files are executable).
I'm writing a quick shell script to build and execute my programs in one fell swoop.
I've gotten that part down, but I'd like to include a little if/else to catch bad extensions - if it's not an .adb (it's an Ada script), it won't let the rest of the program execute.
My two-part question is:
How do I grab just the extension? Or is it easier to just say *.adb?
What would the if/else statement look like? I have limited experience in Bash so I understand that's a pretty bad question.
Thanks!
There are ways to extract the extension, but you don't really need to:
if [[ $filename == *.adb ]] ; then
. . . # this code is run if $filename ends in .adb
else
. . . # this code is run otherwise
fi
(The trouble with extracting the extension is that you'd have to define what you mean by "extension". What is the extension of a file named foo? How about a file named report.2012.01.29? So general-purpose extension-extracting code is tricky, and not worth it if your goal is just to confirm that file has a specific extension.)
There are multiple ways to do it. Which is best depends in part on what the subsequent operations will be.
Given a variable $file, you might want to test what the extension is. In that case, you probably do best with:
extn=${file##*.}
This deletes everything up to the last dot in the name, slashes and all, leaving you with adb if the file name was adafile.adb.
If, on the other hand, you want to do different things depending on the extension, you might use:
case "$file" in
(*.adb) ...do things with .adb files;;
(*.pqr) ...do things with .pqr files;;
(*) ...cover the rest - maybe an error;;
esac
If you want the name without the extension, you can do things the more traditional way with:
base=$(basename $file .adb)
path=$(dirname $file)
The basename command gives you the last component of the file name with the extension .adb stripped off. The dirname command gives you the path leading to the last component of the file name, defaulting to . (the current directory) if there is no specified path.
The more recent way to do those last two operations is:
base=${file##/}
path=${file%/*}
The advantage of these is that they are built-in operations that do not invoke a separate executable, so they are quicker. The disadvantage of the built-ins is that if you have a name that ends with a slash, the built-in treats it as significant but the command does not (and the command is probably giving you the more desirable behaviour, unless you want to argue GIGO).
There are other techniques available too. The expr command is an old, rather heavy-weight mechanism that would not normally be used (but it is very standard). There may be other techniques using the (( ... )), $(( ... )) and [[ ... ]] operators to evaluate various sorts of expression.
To get just the extension from the file path and name, use parameter expansion:
${filename##*.} # deletes everything to the last dot
To compare it with the string adb, just do
if [[ ${filename##*.} != adb ]] ; then
echo Invalid extension at "$filename".
exit 1
fi
or, using 'else`:
if [[ ${filename##*.} != adb ]] ; then
echo Invalid extension at "$filename".
else
# Run the script...
fi
Extension:
fileext=`echo $filename | sed 's_.*\.__'`
Test
if [[ x"${fileext}" = "xadb" ]] ; then
#do something
fi
How do you test if compiled code returns the expected output or fails as expected?
I have worked out a working example below, but it is not easily extendable. Every additional test would require additional nesting parentheses. Of course I could split this into other files, but do you have any suggestions on how to improve this?. Also I'm planning to use this from make test stanza in a makefile, so I do not expect other people to install something that isn't installed by default, just for testing it. And stdout should also remain interleaved with stderr.
simplified example:
./testFoo || echo execution failed
./testBar && echo expected failure
(./testBaz && (./testBaz 2>&1 | cmp -s - foo.tst && ( ./testFoo && echo and so on
|| echo testFoo's execution failed )|| echo testBaz's does not match )
|| echo testBaz's execution failed
my current tester looks like this (for one test):
\#!/bin/bash
compiler1 $1 && (compiler2 -E --make $(echo $1 | sed 's/^\(.\)\(.*\)\..*$/\l\1\2/') && (./$(echo $1 | sed 's/^\(.\)\(.*\)\..*$/\l\1\2/') || echo execution failed) || less $(echo $1 | sed 's/^\(.\)\(.*\)\..*$/\l\1\2/').err) || echo compile failed
I suggest to start looking for patterns here. For example, you could use the file name as the pattern and then create some additional files that encode the expected result.
You can then use a simple script to run the command and verify the result (instead of repeating the test code again and again).
For example, a file testFoo.exec with the content 0 means that it must succeed (or at least return with 0) while testBar.exec would contain 1.
textBaz.out would then contain the expected output. You don't need to call testBaz several times; you can redirect the output in the first call and then look at $? to see if the call succeeded or not. If it did, then you can directly verify the output (without starting the command again).
My own simple minded test harness works like this:
every test is represented by a bash script with an extension .test - these all live in the same directory
when I create a test, I run the test script and examine the output
carefully, if it looks good it goes into a directory called good_results, in a file with the same name as the test that generated it
the main testing script finds all the .test scripts and executes each of them in turn, producing a temporary output file. This is diff'd with the
matching file in the good_results directory and any differences reported
Itv took me about half an hour to write this and get it working, but it has proved invaluable!