How to stop Makefile from expanding my shell output? - makefile

By running this:
GITIGNORE_CONTENTS = $(shell while read -r line; do printf "$$line "; done < "$(.gitignore)")
all:
echo $(GITIGNORE_CONTENTS)
The contents of the variable GITIGNORE_CONTENTS are the expanded version of the ignore patterns on my .gitignore file, i.e., myfile.txt instead of *.txt
I am using this solution because none of the solution on the question Create a variable in a makefile by reading contents of another file work.
This solution works inside a make rule to print the file names, but not put them inside a variable:
all:
while read -r line; do \
printf "$$line "; \
done < ".gitignore"
echo $(GITIGNORE_CONTENTS)

This is an issue of shell's wildcard expansion, not of make's. Consider this,
GITIGNORE_CONTENTS:=$(shell cat .gitignore)
.PHONY: all
all:
echo "$(GITIGNORE_CONTENTS)"
# or alternatively:
#/bin/echo $(GITIGNORE_CONTENTS)

By running this:
GITIGNORE_CONTENTS = $(shell while read -r line; do printf "$$line "; done < ".gitignore")
all:
echo "$(GITIGNORE_CONTENTS)"
With the following .gitignore file:
*.txt
*.var
# Comment
*.xml
*.html
It correctly outputs:
echo "*.txt *.var # Comment *.xml *.html "
*.txt *.var # Comment *.xml *.html

Related

How to create a bash script to make directories and specific files inside each directory

I wrote a bash script trying to generate one directory named after each file inside the directory from which I run the script.
Original directory= /home/agalvez/data//sims/phylip_format
sim1.phylip
sim2.phylip
Directories to create = sim1 sim2
The contents of these new directories should be a copy of the original file that names the new directory and an extra file called "input". This file should contain the name of the .phylip file as well as the following:
"Name of original file"
U
5
Y
/home/agalvez/data/sims/trees/tree_nodenames.txt
After that I want to run the following command (sequentially) in all these new directories:
phylip dollop < input > screenout
My approach is the following one but it is not working:
!/bin/bash
for f in *.phylip;
mkdir /home/agalvez/data/sims/dollop/$f;
cp $f /home/agalvez/data/sims/dollop/$f;
cd /home/agalvez/data/sims/dollop/$f;
echo "$f" | cat > input;
echo "U" | cat >> input;
echo "5" | cat >> input;
echo "Y" | cat >> input;
echo "/home/agalvez/data/sims/trees/tree_nodenames.txt" | cat >> input;
phylip dollop < input > screenout;
;done
Edit: The error messge looks like this:
line 4: syntax error near unexpected token `mkdir'
line 4: ` mkdir /home/agalvez/data/sims/dollop/$f;'
FINAL SOLUTION:
#!/bin/bash
for f in *.phylip;
do
mkdir /home/agalvez/data/sims/dollop/$f;
cp /home/agalvez/data/sims/phylip_format/$f /home/agalvez/data/sims/dollop/$f;
cd /home/agalvez/data/sims/dollop/$f;
echo "$f" | cat > input;
echo "U" | cat >> input;
echo "5" | cat >> input;
echo "Y" | cat >> input;
echo "/home/agalvez/data/sims/trees/tree_nodenames.txt" | cat >> input;
phylip dollop < input > screenout;
done
The immediate problem is that you are lacking a do at the beginning of the loop body; but you'll want to refactor this code to avoid hardcoding the directory structure etc.
The first line needs to start with literally the two characters # and ! in order to be a valid shebang.
Notice also When to wrap quotes around a shell variable?
The printf could be replaced with a here document; I like the compactness of printf here.
#!/bin/bash
for f in *.phylip; do
mkdir -p dollop/"$f"
cp "$f" dollop/"$f"
cd dollop/"$f"
printf "%s\n" "$f" "U" "5" "Y" \
"/home/agalvez/data/sims/trees/tree_nodenames.txt" |
phylip dollop > screenout
done
Going forward, try http://shellcheck.net/ for diagnosing many common beginner problems in shell scripts.
Assuming you have a directory named pingping in your ${HOME} folder with files 1.txt, 2.txt, 3.txt. You can accomplish that like this. Modify this code to suit your needs.
#! /bin/bash
working_directory="${HOME}/pingping/"
cd $working_directory
for f in *.txt
do
mkdir "${f%%.*}"
if [ -f "${f%%.*}.txt" ]
then
if [ -d "${f%%.*}" ]
then
cp ${f%%.*}.txt ${f%%.*}
echo "Done copying"
#phylip dollop < input > screenout
#echo "Succesfully ran the command
fi
else
echo "not found"
fi
done

Turning a list of abs pathed files to a comma delimited string of files in bash

I have been working in bash, and need to create a string argument. bash is a newish for me, to the point that I dont know how to build a string in bash from a list.
// foo.txt is a list of abs file names.
/foo/bar/a.txt
/foo/bar/b.txt
/delta/test/b.txt
should turn into: a.txt,b.txt,b.txt
OR: /foo/bar/a.txt,/foo/bar/b.txt,/delta/test/b.txt
code
s = ""
for file in $(cat foo.txt);
do
#what goes here? s += $file ?
done
myShellScript --script $s
I figure there was an easy way to do this.
with for loop:
for file in $(cat foo.txt);do echo -n "$file",;done|sed 's/,$/\n/g'
with tr:
cat foo.txt|tr '\n' ','|sed 's/,$/\n/g'
only sed:
sed ':a;N;$!ba;s/\n/,/g' foo.txt
This seems to work:
#!/bin/bash
input="foo.txt"
while IFS= read -r var
do
basename $var >> tmp
done < "$input"
paste -d, -s tmp > result.txt
output: a.txt,b.txt,b.txt
basename gets you the file names you need and paste will put them in the order you seem to need.
The input field separator can be used with set to create split/join functionality:
# split the lines of foo.txt into positional parameters
IFS=$'\n'
set $(< foo.txt)
# join with commas
IFS=,
echo "$*"
For just the file names, add some sed:
IFS=$'\n'; set $(sed 's|.*/||' foo.txt); IFS=,; echo "$*"

How to loop over a list of directories containing wildcards in bash?

Assuming that I have a directory which contains decades of subdirectories:
$ ls -d /very/long/path/*/
adir1/ adir2/ b2dir/ b3dir/ k101/ k102/ k103/ k104/ k220/ k221/ k222/ etc
I would like to loop over a selection of directories which will be defined "dynamically" based on the answer given by the user and it will contain wildcards. For example (the code that doesn't work):
$ cat my_script.sh
DATADIR="/very/long/path"
echo -n "Select dirs to involve: "
read dirlist
for DIR in "$dirlist"; do
echo $DATADIR/$DIR
[do stuff]
...
done
What would be desired is the following:
$ ./my_script.sh
Select dirs to involve: a* k10?
/very/long/path/adir1
/very/long/path/adir2
/very/long/path/k101
/very/long/path/k102
/very/long/path/k103
/very/long/path/k104
Any hint?
Not sure if this would run into problems, but give it a whirl:
DATADIR="/very/long/path"
read -r -p "Select dirs to involve: " -a dirs
cd $DATADIR
for dir in ${dirs[#]}
do
echo "$dir"
done
Leaving the array unquoted allows for the globs to expand.
UPDATE: Dual loop to allow for using directory location
DATADIR="/very/long/path"
read -r -p "Select dirs to involve: " -a dirs
for item in "${dirs[#]}"
do
for dir in "$DATADIR/"$item
do
echo "$dir"
done
done
One potential solution is to use find :
#!/bin/bash
DATADIR="/very/long/path"
echo -n "Select matching expression: "
IFS= read -r dirlist
while IFS read -r -d '' DIR; do
echo "$DIR"
[do stuff]
...
done < <(find "$DATADIR" -path "$dirlist" -print0)
I would advise that you read the manpage of find, as matching will eat up slashes, and might not behave as you are used to with shell globbing.
Please note that using the -print0 and read -d'' is a way to make sure files with funny names (whitespace, newlines) are handled without issues.
If you want to be able to handle several expressions input at the same time, you would have to do something like this :
#!/bin/bash
DATADIR="/very/long/path"
echo -n "Select matching expression: "
IFS= read -r -a dirlist_array
for dirlist in "${dirlist_array[#]}" ; do
while IFS read -r -d '' DIR; do
echo "$DIR"
[do stuff]
...
done < <(find "$DATADIR" -path "$dirlist" -print0)
done

Bash - Printf an array containing directories not displaying

I find all the directories I need and Display them:
mapfile -t sm < <(find /home/user/ -name "sourcemod" -type d| egrep '/home/user/[a-zA-Z0-9]+/[0-9]+/csgo/a$
echo "SourceMod directories:"
printf "%s \n" "${sm[#]}"
echo "==============================================================="
Here I try remove /home/user/ using sed
mapfile -t dir < <("${sm[#]}"|sed 's,/home/user/,,')
printf "%s \n" "${dir[#]}"
But I get this output:
./main.sh: line 15: /home/user/john/224/csgo/addons/sourcemod: Is a
directory
How do I properly display the dir array like this:
user/224/csgo/addons/sourcemod
user1/208/csgo/addons/sourcemod
user/209/csgo/addons/sourcemod
Replace:
mapfile -t dir < <("${sm[#]}"|sed 's,/home/user/,,')
with:
mapfile -t dir < <(printf '%s\n' "${sm[#]}" | sed 's,/home/user/,,')
You asked it to execute the first directory in the list as a command with the arguments being the rest of the directories in the list. Using printf() echoes each directory on its own line.
Beware directory names containing spaces or newlines.

Shell script to get one to one map and rename the filename

I have 2 files sorted by numerically. I need help with shell script to read these 2 files and do a 1:1 mapping and rename the filenames with the mapped case#;
For example:
cat case.txt
10_80
10_90
cat files.txt
A BCD_x 1.pdf
A BCD_x 2.pdf
ls pdf_dir
A BCD_x 1.pdf A BCD_x 2.pdf
Read these 2 txt and rename the pdf files in pdf_dir :
A BCD_x 1.pdf as A BCD_10_80.pdf
A BCD_x 1.pdf as A BCD_10_90.pdf
Use paste to create the "mapping", then shell facilities to do the renaming.
shopt -s extglob
while IFS=$'\t' read file replacement; do
echo mv "$file" "${file/x +([0-9])/$replacement}"
done < <(paste files.txt case.txt)
remove "echo" when you're satisfied.
Using awk:
awk 'FNR==NR{a[FNR]=$0;next}
{f=$0; sub(/_x /, "_" a[FNR] " "); system("mv \"" f "\" \"" $0 "\"")}' case.txt files.txt
Using normal array and sed substitution -
Removing echo before mv will provide you the move capability.
You can change the /path/to/pdf_dir/ to specify your path to desired directory
#!/bin/bash
i=0
while read line
do
arr[i]="$line"
((i=i+1));
done < files.txt
i=0
while read case
do
newFile=$(echo "${arr[i]}" | sed "s/x/"$case"/")
echo mv /path/to/pdf_dir/"${arr[i]}" /path/to/pdf_dir/"$newFile"
((i=i+1))
done < case.txt
If you have Bash 4.0 this could help:
#!/bin/bash
declare -A MAP
I=0
IFS=''
while read -r CASE; do
(( ++I ))
MAP["A BCD_x ${I}.pdf"]="A BCD_${CASE}.pdf"
done < case.txt
while read -r FILE; do
__=${MAP[$FILE]}
[[ -n $__ ]] && echo mv "$FILE" "$__" ## Remove echo when things seem right already.
done < files.txt
Note: Make sure you run the script in UNIX file format.

Resources