How to extract folder name as a variable in bash - bash

I have directory
MainProject/src/
My script which I am calling test.sh run at /MainProject/ and here is some part of the sript:
dotnet restore src/*.sln
dotnet msbuild -t:publish src/*.sln -p:Configuration=Release
For this command, I want MainProject.Test as variable VAR:
dotnet vstest VAR/bin/Release/netcoreapp1.1/VAR.dll
or something like this:
dotnet vstest {src/*.Test}/bin/Release/netcoreapp1.1/{src/*Test}.dll
Which contains these files and folders:
Files:
project.sln
somescript.sh
Folders:
MainProject.Test
MainProject.Host
What I need to do is fetch MainProject.Test folder name and set it to a variable, but I also need it to be templatized where I can set it to a variable using something like *.Test
The reason for this is that I need the script parametrized because there are
MainProject2
MainProject3
MainProjectx
using the same naming convention.

The current directory is in $PWD. That's fully-qualified; to remove everything from the beginning up to the last / would be ${PWD##*/}, using a parameter expansion.
Thus, to extract the last piece of the current working directory and add .Test as a suffix:
result=${PWD##*/}.Test

Related

dockerfile copy list of files, when list is taken from a local file

I've got a file containing a list of paths that I need to copy by Dockerfile's COPY command on docker build.
My use case is such: I've got a python requirements.txt file, when inside I'm calling multiple other requirements files inside the project, with -r PATH.
Now, I want to docker COPY all the requirements files alone, run pip install, and then copy the rest of the project (for cache and such). So far i haven't managed to do so with docker COPY command.
No need of help on fetching the paths from the file - I've managed that - just if it is possible to be done - how?
thanks!
Not possible in the sense that the COPY directive allows it out of the box, however if you know the extensions you can use a wildcard for the path such as COPY folder*something*name somewhere/.
For simple requirements.txt fetching that could be:
# but you need to distinguish it somehow
# otherwise it'll overwrite the files and keep the last one
# e.g. rename package/requirements.txt to package-requirements.txt
# and it won't be an issue
COPY */requirements.txt ./
RUN for item in $(ls requirement*);do pip install -r $item;done
But if it gets a bit more complex (as in collecting only specific files, by some custom pattern etc), then, no. However for that case simply use templating either by a simple F-string, format() function or switch to Jinja, create a Dockerfile.tmpl (or whatever you'd want to name a temporary file), then collect the paths, insert into the templated Dockerfile and once ready dump to a file and execute afterwards with docker build.
Example:
# Dockerfile.tmpl
FROM alpine
{{replace}}
# organize files into coherent structures so you don't have too many COPY directives
files = {
"pattern1": [...],
"pattern2": [...],
...
}
with open("Dockerfile.tmpl", "r") as file:
text = file.read()
insert = "\n".join([
f"COPY {' '.join(values)} destination/{key}/"
for key, values in files.items()
])
with open("Dockerfile", "w") as file:
file.write(text.replace("{{replace}}", insert))
You might want to do this for example:
FROM ...
ARG files
COPY files
and run with
docker build -build-args items=`${cat list_of_files_to_copy.txt}`

Cmake File COPY across drives

The following does not work:
cmake file ( copy "C:/pathtofile/file.file" DESTINATION "D:/pathtofile2/file2.file" )
Is there a way to achieve the same thing using cmake?
The documentation states that DESTINATION has to be a directory. Therefore renaming the file does not work this way.
execute_process(COMMAND ${CMAKE_COMMAND} -E copy "C:/pathtofile/file.file" "D:/pathtofile2/file2.file")
should work.
cmakes fantastic Configure file system will work. The files can have different names, but basically if it finds any variables that can be expanded by cmake like ${cmake_root_dir} or $ENV{localappdata}, it will edit the file, replace the variables with their values and save it to the new location under the new name. So if there are no variables it just copies.
Configure_file( C:/pathtofile/file.file" "D:/pathtofile2/file2.file")
If you have cmake style variables in there for whatever reason and you don't want them expanded, use #Only as it will only expand variables surrounded by "#":
Configure_file( C:/pathtofile/file.file" "D:/pathtofile2/file2.file" #ONLY)

Using CMake, how can I concat files and install them

I'm new to CMake and I have a problem that I can not figure out a solution to. I'm using CMake to compile a project with a bunch of optional sub-dirs and it builds shared library files as expected. That part seems to be working fine. Each of these sub-dirs contains a sql file. I need to concat all the selected sql files to one sql header file and install the result. So one file like:
sql_header.sql
sub_dir_A.sql
sub_dir_C.sql
sub_dir_D.sql
If I did this directly in a make file I might do something like the following only smarter to deal with only the selected sub-dirs:
cat sql_header.sql > "${INSTALL_PATH}/somefile.sql"
cat sub_dir_A.sql >> "${INSTALL_PATH}/somefile.sql"
cat sub_dir_C.sql >> "${INSTALL_PATH}/somefile.sql"
cat sub_dir_D.sql >> "${INSTALL_PATH}/somefile.sql"
I have sort of figured out pieces of this, like I can use:
LIST(APPEND PACKAGE_SQL_FILES "some_file.sql")
which I assume I can place in each of the sub-dirs CMakeLists.txt files to collect the file names. And I can create a macro like:
CAT(IN "${PACKAGE_SQL_FILES}" OUT "${INSTALL_PATH}/somefile.sql")
But I am lost between when the CMake initially runs and when it runs from the make install. Maybe there is a better way to do this. I need this to work on both Windows and Linux.
I would be happy with some hints to point me in the right direction.
You can create the concatenated file mainly using CMake's file and function commands.
First, create a cat function:
function(cat IN_FILE OUT_FILE)
file(READ ${IN_FILE} CONTENTS)
file(APPEND ${OUT_FILE} "${CONTENTS}")
endfunction()
Assuming you have the list of input files in the variable PACKAGE_SQL_FILES, you can use the function like this:
# Prepare a temporary file to "cat" to:
file(WRITE somefile.sql.in "")
# Call the "cat" function for each input file
foreach(PACKAGE_SQL_FILE ${PACKAGE_SQL_FILES})
cat(${PACKAGE_SQL_FILE} somefile.sql.in)
endforeach()
# Copy the temporary file to the final location
configure_file(somefile.sql.in somefile.sql COPYONLY)
The reason for writing to a temporary is so the real target file only gets updated if its content has changed. See this answer for why this is a good thing.
You should note that if you're including the subdirectories via the add_subdirectory command, the subdirs all have their own scope as far as CMake variables are concerned. In the subdirs, using list will only affect variables in the scope of that subdir.
If you want to create a list available in the parent scope, you'll need to use set(... PARENT_SCOPE), e.g.
set(PACKAGE_SQL_FILES
${PACKAGE_SQL_FILES}
${CMAKE_CURRENT_SOURCE_DIR}/some_file.sql
PARENT_SCOPE)
All this so far has simply created the concatenated file in the root of your build tree. To install it, you probably want to use the install(FILES ...) command:
install(FILES ${CMAKE_BINARY_DIR}/somefile.sql
DESTINATION ${INSTALL_PATH})
So, whenever CMake runs (either because you manually invoke it or because it detects changes when you do "make"), it will update the concatenated file in the build tree. Only once you run "make install" will the file finally be copied from the build root to the install location.
As of CMake 3.18, the CMake command line tool can concatenate files using cat. So, assuming a variable PACKAGE_SQL_FILES containing the list of files, you can run the cat command using execute_process:
# Concatenate the sql files into a variable 'FINAL_FILE'.
execute_process(COMMAND ${CMAKE_COMMAND} -E cat ${PACKAGE_SQL_FILES}
OUTPUT_VARIABLE FINAL_FILE
WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}
)
# Write out the concatenated contents to 'final.sql.in'.
file(WRITE final.sql.in ${FINAL_FILE})
The rest of the solution is similar to Fraser's response. You can use configure_file so the resultant file is only updated when necessary.
configure_file(final.sql.in final.sql COPYONLY)
You can still use install in the same way to install the file:
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/final.sql
DESTINATION ${INSTALL_PATH})

How to copy only new files using bash scripting

I have to use bash scripting to copy files from one folder to another. If the destination folder has a file with the same name but older timestamp, it should not copy. Only newer files should be copied. I could have used cp -u, but I was asked not to use it. Essentially I have to use the test command testing for "ot". Please let me know how could this be done. I believe two for loops one to read the files in the source and one for the destination directories can be used and the the time stamp compared. The problem is that both for loops produce the absolute path names along with the file name. So not sure how to compare them
Thanks
You can profit from the parameter substitution:
for file in "$folder1"/* ; do
filename=${file##*/} # Remove everything to the last slash.
Or, you can change the directory:
cd "$folder1"
for file in * ; do
## you have to use full or relative path to $folder2 here

How to output a list of files on bash command line

I am using handlebars.js to render client-side templates (using Jade on the node.js server-side) for our single-page app.
I want to precompile the handlebars templates on the server side and bundle them up as one JS file to send to the client.
Currently, I use handlebars to compile templates like so:
$ handlebars template1.handlebars template2.handlebars -f precompiled_templates.js
I'd like to write a bash script that can read all the *.handlebars files in a directory and then run them through the handlebars compiler. So, if I had a directory like so:
templates/
temp1.handlebars
temp2.handlebars
temp3.handlebars
temp4.handlebars
Running my bash script (or, one line command) in the templates directory would essentially run the following handlebars command:
$ handlebars temp1.handlebars temp2.handlebars temp3.handlebars temp4.handlebars -f precompiled_templates.js
Does anyone know how I could write a bash script to get all the handlebars files in a directory onto the command line like above?
It looks like you want something similar to
handlebars templates/*.handlebars -f precompiled_templates.js
except you would end up with 'templates' prefixed to each file. My preferred approach requires two lines:
files=( templates/*.handlebars )
handlebars "${files[#]#templates/}" -f precompiled_templates.js.
The first line puts all the desired files in an array. In the second line, we expand the contents of the array, but stripping the "templates/" prefix from each element in the resulting expansion.
In the templates directory do:
handlebars `find -name *.handlebars` -f precompiled_templates.js
The back-ticks means it will execute that command then return the result, so you in effect you run handlebars on each file it returns. As written above find will look in current and subdirectories for the files so make sure you run it from the right place.
In Bash, lists can be create with string within double quote :
FILES="/etc/hosts /etc/passwd"
for file in $FILES; do cat $file ; done
<cat all files>
You can also use find and exec commands.
man [find|exec] for more informations

Resources