Pass the stdout of `git diff --name-only` to an executable file as parameters of the file - bash

So, there's this executable file called pint (it's part of the Laravel 9 framework). It formats PHP files according to a configurable standard like PSR12.
Anyway, you can pass a list of the files you want to format as the parameters of pint like this:
pint file1 file2 file3 file4 ...
If you pass no argument, pint formats all files of the project.
So, what I'm trying to do is that I want pint to format only the files that have changed since the last commit. In other words, I want the output of git diff --name-only HEAD^ HEAD to be passed as parameters of pint in bash shell.
So, this is what I could come up with, but sadly it doesn't work:
git diff --name-only HEAD^ HEAD -o /dev/stdout | ./vendor/bin/pint
Which says: fatal: /dev/stdout: '/dev/stdout' is outside repository at '/home/user/directory' and then it proceeds to execute ./vendor/bin/pint normally as if no argument had been passed.
I suppose I should somehow convert new line to space before passing it to pint but I'm not sure.

| is for piping to standard input, but pint expects the filenames to be command-line arguments, not stdin.
Use $(...) to substitute the output of a command into the command line.
./vendor/bin/pint $(git diff --name-only HEAD^ HEAD)
Note that this won't work if any of the filenames contain whitespace, since the spaces will be treated as filename delimiters.

Related

Git Extract modification in one string line

Any good combination of Bash + git diff to get in one line the only change that I made in my file?
I'm using form Jenkins DSL, and the best that I get so far is this
"${sh(script: "git diff --shortstat", returnStdout: true)}".trim() == "1 file changed, 1 insertion(+), 1 deletion(-)"
But what I would love to have is the "hello world" text that I just add into one of the files.
If you've got just one hunk,
git diff -U0 | sed 1,/^##/d
and strip the leading character off.

How to modify git log pretty format to strip out certain characters (like commas) from one of the fields?

I have a bash script that creates a CSV file with the git logs for a set of subdirectories across some repos, using git log's --pretty format to put each commit on one line with a few variables from my script as well as some of the built-in options.
The basic format of --pretty I'm using is as follows:
git log --pretty="$PWD,${current_dir},%h,%cs,%cN,%s"
Within the bash script, the full line looks like:
(git log --no-merges --after="${number_of_days} days ago" --pretty="$PWD,${current_dir},%h,%cs,%cN,%s" -- "$current_dir") >> "$starting_dir"/export.csv
Here's an example of what the CSV looks like:
/path/to/parent,./NameOfSubdirectory,bdacd7e,2021-03-24,kclemson,commit message,with,extra,commas,in it
/path/to/parent,./NameOfSubdirectory,45cb4a0,2021-03-24,kclemson,commit message foo
/path/to/parent,./NameOfSubdirectory,9f8294b,2021-03-24,kclemson,commit message bar
/path/to/parent,./NameOfSubdirectory,3e91e92,2021-03-24,kclemson,commit message baz
This works fine most of the time, but of course commas in the %s messes up the formatting when the CSV is opened in Excel. What I'd like to have it do instead is replace any commas in %s with a space, so it would look like this:
/path/to/parent,./NameOfSubdirectory,bdacd7e,2021-03-24,kclemson,commit message with extra commas in it
/path/to/parent,./NameOfSubdirectory,45cb4a0,2021-03-24,kclemson,commit message foo
/path/to/parent,./NameOfSubdirectory,9f8294b,2021-03-24,kclemson,commit message bar
/path/to/parent,./NameOfSubdirectory,3e91e92,2021-03-24,kclemson,commit message baz
What is the right way to do this? I assume I need to pass it to sed, but I'm looking for help on both the sed syntax to swap in a " " for a "," in that field, and also how to do that within the bash script.
Thanks.
Instead of , use some other delimiter that is unlikely to occur in the subject, like ^^^. Replace , with a white space first, and then replace ^^^ with ,.
git log --pretty="$PWD^^^${current_dir}^^^%h^^^%cs^^^%cN^^^%s" | \
sed -e 's/,/ /g' | sed -e 's/\^^^/,/g'

Diff command in Bash Shell Scipt

Can someone please explain the meaning of the below statement.
diff -u file1 -
From what i understand, diff commands shows the differences between two input files. However, in the above scenario, it takes only one input file.
From the man page for diff:
If a FILE is '-', read standard input.
So the command diff -u file1 - reads file1, and reads everything from standard input (probably being piped from some other program), and compares the two.

how to make a winmerge equivalent in linux

My friend recently asked how to compare two folders in linux and then run meld against any text files that are different. I'm slowly catching on to the linux philosophy of piping many granular utilities together, and I put together the following solution. My question is, how could I improve this script. There seems to be quite a bit of redundancy and I'd appreciate learning better ways to script unix.
#!/bin/bash
dir1=$1
dir2=$2
# show files that are different only
cmd="diff -rq $dir1 $dir2"
eval $cmd # print this out to the user too
filenames_str=`$cmd`
# remove lines that represent only one file, keep lines that have
# files in both dirs, but are just different
tmp1=`echo "$filenames_str" | sed -n '/ differ$/p'`
# grab just the first filename for the lines of output
tmp2=`echo "$tmp1" | awk '{ print $2 }'`
# convert newlines sep to space
fs=$(echo "$tmp2")
# convert string to array
fa=($fs)
for file in "${fa[#]}"
do
# drop first directory in path to get relative filename
rel=`echo $file | sed "s#${dir1}/##"`
# determine the type of file
file_type=`file -i $file | awk '{print $2}' | awk -F"/" '{print $1}'`
# if it's a text file send it to meld
if [ $file_type == "text" ]
then
# throw out error messages with &> /dev/null
meld $dir1/$rel $dir2/$rel &> /dev/null
fi
done
please preserve/promote readability in your answers. An answer that is shorter but harder to understand won't qualify as an answer.
It's an old question, but let's work a bit on it just for fun, without thinking in the final goal (maybe SCM) nor in tools that already do this in a better way. Just let's focus in the script itself.
In the OP's script, there are a lot of string processing inside bash, using tools like sed and awk, sometimes more than once in the same command line or inside a loop executing n times (one per file).
That's ok, but it's necessary to remember that:
Each time the script calls any of those programs, it's created a new process in the OS, and that is expensive in time and resources. So the less programs are called, the better is the performance of script that is executing:
diff 2 times (1 just to print to user)
sed 1 time processing diff result and 1 time for each file
awk 1 time processing sed result and 2 times for each file (processing file result)
file 1 time for each file
That doesn't apply to echo, read, test and others that are builtin commands of bash, so no external program is executed.
meld is the final command that will display the files to user, so it doesn't count.
Even with the builtin commands, redirection pipelines | has a cost too, because the shell has to create pipes, duplicate handles, and maybe even creating forks of the shell (that is a process itself). So again: less is better.
The messages of diff command are locale dependants, so if the system is not in english, the whole script won't work.
Thinking that, let's clean a bit the original script, mantaining the OP's logic:
#!/bin/bash
dir1=$1
dir2=$2
# Set english as current language
LANG=en_US.UTF-8
# (1) show files that are different only
diff -rq $dir1 $dir2 |
# (2) remove lines that represent only one file, keep lines that have
# files in both dirs, but are just different, delete all but left filename
sed '/ differ$/!d; s/^Files //; s/ and .*//' |
# (3) determine the type of file
file -i -f - |
# (4) for each file
while IFS=":" read file file_type
do
# (5) drop first directory in path to get relative filename
rel=${file#$dir1}
# (6) if it's a text file send it to meld
if [[ "$file_type" =~ "text/" ]]
then
# throw out error messages with &> /dev/null
meld ${dir1}${rel} ${dir2}${rel} &> /dev/null
fi
done
A little explaining:
Unique chain of commands cmd1 | cmd2 | ... where the output (stdout) of previous one is the input (stdin) of the next one.
Execute sed just once to execute 3 operations (separated with ;) in diff output:
Deleting lines ending with " differ"
Delete "Files " at the beginning of remaining lines
Delete from " and " to the end of remaining lines
Execute command file once to process the file list in stdin (option -f -)
Use the while bash sentence to read two values separated by : for each line line of stdin.
Use bash variable substitution to extract filename from a variable
Use bash test to compare a file type with a regular expression
For clarity reasons, I didn't considerate that file and directory names may have spaces. In such cases, both scripts will fail. To avoid that is necessary enclose in double quotes any reference to file/dir name variable.
I didn't use awk, because it is powerful enough that can replace almost the entire script ;-)

How to archive files under certain dir that are not text files in Mac OS?

Hey, guys, I used zip command, but I only want to archive all the files except *.txt. For example, if two dirs file1, file2; both of them have some *.txt files. I want archive only the non-text ones from file1 and file2.
tl;dr: How to tell linux to give me all the files that don't match *.txt
$ zip -r zipfile -x'*.txt' folder1 folder2 ...
Move to you desired directory and run:
ls | grep -P '\.(?!txt$)' | zip -# zipname
This will create a zipname.zip file containing everything but .txt files. In short, what it does is:
List all files in the directory, one per line (this can be achieved by using the -1 option, however it is not needed here as it's the default when output is not the terminal, it is a pipe in this case).
Extract from that all lines that do not end in .txt. Note it's grep using a Perl regular expression (option -P) so the negative lookahead can be used.
Zip the list from stdin (-#) into zipname file.
Update
The first method I posted fails with files with two ., like I described in the comments. For some reason though, I forgot about the -v option for grep which prints only what doesn't match the regex. Plus, go ahead and include a case insensitive option.
ls | grep -vi '\.txt$' | zip -# zipname
Simple, use bash's Extended Glob option like so:
#!/bin/bash
shopt -s extglob
zip -some -options !(*.txt)
Edit
This isn't as good as the -x builtin option to zip but my solution is generic across any command that may not have this nice feature.

Resources