string replacement using xargs - shell

I am trying to collect file path and timestamp for each file under a certain directory(which is passed as an argument) in makefile
So, it goes like this.
TIMESTAMP_LOG := timestamp.log
TARGET_ROOT := ../../out/root
define collect-timestamp
$(shell find $(1) | xargs -lfn sh -c 'echo -n fn"," >> $(TIMESTAMP_LOG); stat -c %Y fn >> $(TIMESTAMP_LOG)')
endef
all:
$(call collect-timestamp, $(TARGET_ROOT))
If I run this, i would the get whole file path and timestamp as below
ex) ../../out/root/bin/ls,133030303
but I want to get rid of "../../out/root" in file path.(passing as an argument if possible)
I thought I could do this using sed or shell script(see below) but apparently I am stuck. I tried:
$(shell find $(1) | xargs -Ifn sh -c 'echo -n ${fn##$(1)}"," >> $(TIMESTAMP_LOG); stat -c %Y fn >> $(TIMESTAMP_LOG)')
$(shell find $(1) | xargs -Ifn sh -c 'sed 's/fn//g' >> $(TIMESTAMP_LOG); stat -c %Y fn >> $(TIMESTAMP_LOG)')

If you use find, you don't need xargs in most cases.
The following should work:
find $(1) -exec stat -c "%n,%Y" {} \; | sed 's#$(1)\/##'
Note that I use $(1) as both parameter to find and in sed substitution command.

You may try
find $(1) | xargs stat -c %Y >> $(TIMESTAMP_LOG)

Related

How to add a specific folder tree to the fzf search list?

I use fzf in bash on Gentoo Linux and configured it in the ~/.bashrc with
if [ -x "$(command -v fzf)" ]
then
source /usr/share/fzf/key-bindings.bash
fi
export FZF_DEFAULT_OPTS="
--layout=reverse
--info=inline
--height=80%
--multi
--preview-window=:hidden
--preview '([[ -f {} ]] && (bat --style=numbers --color=always {} || cat {})) || ([[ -d {} ]] && (tree -C {} | less)) || echo {} 2> /dev/null | head -200'
--color='hl:148,hl+:154,pointer:032,marker:010,bg+:237,gutter:008'
--prompt='∼ ' --pointer='▶' --marker='✓'
--bind 'ctrl-p:toggle-preview'
--bind 'ctrl-a:select-all'
--bind 'ctrl-y:execute-silent(echo {+} | pbcopy)'
--bind 'ctrl-e:execute(echo {+} | xargs -o vim)'
--bind 'ctrl-v:execute(code {+})'
"
CTRL-T should now provide a fuzzy search in ~/my/.. and in /usr/local/.
What is the best way to teach fzf about these two directory trees?
Short Answer:
You should use the variable FZF_CTRL_T_COMMAND and set your custom command.
In this case you should use something like this:
export FZF_CTRL_T_COMMAND="find ~/my/.. /usr/local"
And now, when you press Ctrl-T you will be able to search only in those directories.
Long Answer:
I realized that you should use FZF_CTRL_T_COMMAND variable by inspecting into the key-bindings file, in my case, this one is located in: /usr/share/bash-completion/completions/fzf-key-bindings in your case might be /usr/share/fzf/key-bindings.bash.
If you check in that file you should see a function called __fzf_select__ and inside this one you might have something like this:
__fzf_select__() {
local cmd opts
cmd="${FZF_CTRL_T_COMMAND:-"command find -L . -mindepth 1 \\( -path '*/\\.*' -o -fstype 'sysfs' -o -fstype 'devfs' -o -fstype 'devtmpfs' -o -fstype 'proc' \\) -prune \
-o -type f -print \
-o -type d -print \
-o -type l -print 2> /dev/null | cut -b3-"}"
opts="--height ${FZF_TMUX_HEIGHT:-40%} --bind=ctrl-z:ignore --reverse $FZF_DEFAULT_OPTS $FZF_CTRL_T_OPTS -m"
eval "$cmd" |
FZF_DEFAULT_OPTS="$opts" $(__fzfcmd) "$#" |
while read -r item; do
printf '%q ' "$item" # escape special chars
done
}
As you can see, the assignation in the third line: cmd="${FZF_CTRL_T_COMMAND:-"command find -L . ..."}" means that the command to execute will be what FZF_CTRL_T_COMMAND variable has, but if this is empty then it should execute the function defined there: command find -L . ..
Maybe you should take a look at the assignment command find -L ... and set your FZF_CTRL_T_COMMAND similarly or you can simply set it as I specified in the short answer and it will work: export FZF_CTRL_T_COMMAND="find ~/my/.. /usr/local"

Why I am not getting a value when i call a function within another in a bash script

I have a function that generates a random file name
#generate random file names
get_rand_filename() {
if [ "$ASCIIONLY" == "1" ]; then
for ((i=0; i<$((MINFILENAMELEN+RANDOM%MAXFILENAMELEN)); i++)) {
printf \\$(printf '%03o' ${AARR[RANDOM%aarrcount]});
}
else
# no need to escape double quotes for filename
cat /dev/urandom | tr -dc '[ -~]' | tr -d '[$></~:`\\]' | head -c$((MINFILENAMELEN+RANDOM%MAXFILENAMELEN)) #| sed 's/\(["]\)/\\\1/g'
fi
printf "%s" $FILEEXT
}
export -f get_rand_filename
When I call it from within another function
cf(){
fD=$1
echo "the target dir recieved is " $fD
CFILE="$(get_rand_filename)"
echo "the file name is "$CFILE
}
export -f cf
when I call
echo "$targetdir" | xargs -0 sh -c 'cf $1' sh
I only get the FILEXT (no random file name)
when I call
cf "$targetdir"
I get a valid result
I need to be able to handle spaces in the $targetdir and file name string.
echo "$targetdir" | xargs -0 sh -c 'cf $1' sh
You should invoke bash rather than sh. Function exporting is a bash feature.
$ foo() { echo bar; }
$ export -f foo
$ sh -c 'foo'
sh: 1: foo: not found
$ bash -c 'foo'
bar
Also, get rid of the -0 option since the input isn't NUL-separated. Use -d'\n' instead. And quote "$1" for robustness.
echo "$targetdir" | xargs -d'\n' bash -c 'cf "$1"' bash
Actually, you could use -0 if you change the input format.
printf '%s\0' "$targetdir" | xargs -0 bash -c 'cf "$1"' bash
For what it's worth, mktemp creates random temporary files, and does it safely. It makes sure the file doesn't already exist and then creates it to prevent anybody else from snatching up the name in the split second between the name being generated and it being returned to the caller.

for loop with standard output

I want to run two scripts (gunzip and fastx_collapser) for multiple input files. The output from gunzip should be input to fastx_collapser. How do I do this in a loop function?
My try:
for f in *.gz gunzip -c "$f" | fastx_collapser -Q33 -z -o "${f%}.coll.gz"
You need a do and a done (and some semicolons if you insist on a single line):
for f in *.gz
do
gunzip -c "$f" | fastx_collapser -Q33 -z -o "${f%}.coll.gz"
done
or:
for f in *.gz; do gunzip -c "$f" | fastx_collapser -Q33 -z -o "${f%}.coll.gz"; done

Makefile with multiple values for a parameter

Currently, I am working on a makefile that takes a parameter "CLASS=xxx" and then compiles and does stuff with that value.
In the end, it runs an application ($APP) on a bunch of files.
I enter this command:
make default CLASS=Test_UART
and the makefile processes it thusly:
pc: $(APP)
make -C BUILDENV CLASS=$(CLASS) BUILD=just_filelist OUTPUT=filelist.txt SKIPSELF=yes
../classCvt/classCvt <./Applications/$(CLASS).class> ./Applications/$(CLASS).ujc
time -p ./$(APP) ./Applications/$(CLASS).ujc `cat filelist.txt`
Hence it calls a makefile in my BUILDENV folder which does the following:
#USAGE: make -C <PATH_TO_THIS_FILES_PARENT_DIR> CLASS=<MY_JAVA_FILE_NAME_WITHOUT_JAVA_EXTENSION> OUT=<OUTPUT_FILE_NAME>
SELF := $(dir $(CURDIR)/$(word $(words $(MAKEFILE_LIST)),$(MAKEFILE_LIST)))
CLASS ?= PLEASE_SPECIFY_CLASS_PARAM
DIR := $(PWD)#print working directory
CCVT ?= $(SELF)/../../classCvt/classCvt
TOBIN ?= $(SELF)/../../classCvt/tobin
OUTPUT ?=
### Comment: Defining CMD.
ifeq ($(BUILD), just_filelist)
CMD = echo
else
ifeq ($(BUILD), PC)
CMD = echo
else
ifeq ($(BUILD), unopt)
CMD = $(TOBIN)
else
### Comment: Optimized CMD = tobin -c ccvt
CMD = $(TOBIN) -c $(CCVT)
endif
endif
endif
ifeq ($(OUTPUT), )
OUT = &1
else
OUT = $(DIR)/$(OUTPUT)
endif
ifeq ($(SKIPSELF), yes)
MYCLASS =
else
MYCLASS = $(DIR)/Applications/$(CLASS).class
endif
all:
CLASSPATH=$(SELF)/RT/real:$(SELF)/RT/fake:$(DIR) javac $(DIR)/Applications/$(CLASS).java
find $(SELF)/RT/real -iname "*.class" -type f > $(SELF)/files
ls $(DIR)/Applications/*.class | grep -v "$(CLASS)\.class" >> $(SELF)/files || true
cat $(SELF)/files | xargs $(CMD) $(MYCLASS) >$(OUT)
rm -f $(SELF)/files
What I would like to do is give a command like:
make default CLASS=Test1,Test2,Test3
and the makefile to process it for the 3 classes and put the given classes in a .txt and the default classes in a different .txt, something like this like this:
pc: $(APP)
make -C BUILDENV default_classes BUILD=list_default_classes OUTPUT=list_default_classes.txt
# make -C BUILDENV given_classes BUILD=list_given_classes OUTPUT=list_given_classes.txt CLASS=$(CLASS) SKIPSELF=yes
../classCvt/classCvt `cat list_given_classes.txt`./Applications/$(CLASS).ujc
#here the list_given_classes should now contain the .ujc files
time -p ./$(APP) `cat list_given_classes.txt` `cat list_default_classes.txt`
and for the makefile in the BUILDENV, I expect something like:
default_classes:
CLASSPATH=$(SELF)/RT/real:$(SELF)/RT/fake:$(DIR)
find $(SELF)/RT/real -iname "*.class" -type f > $(SELF)/files
ls $(DIR)/Applications/*.class | grep -v "$(CLASS1)\.class" "$(CLASS2)\.class">> $(SELF)/files || true
cat $(SELF)/files | xargs $(CMD) >$(OUT)
rm -f $(SELF)/files
given_classes:
javac $(DIR)/Applications/$(CLASS).java
find $(SELF)/RT/real -iname "*.class" -type f > $(SELF)/files
ls $(DIR)/Applications/*.class | grep -v "$(CLASS)\.class" >> $(SELF)/files || true
cat $(SELF)/files | xargs $(CMD) $(MYCLASS) >$(OUT)
rm -f $(SELF)/files
However, I'm not sure how to do this for a CLASS parameter containing multiple classes.
I'm thinking to try and parse the Test1,Test2,Test3 value into a list of 1,2,3 and then iterating over it. But no clue if this is a good way and even on how to do it.
What do you guys suggest?
Pretty way:
pc: $(APP)
define BUILD_CLASS
pc: pc-$(CLASS_SPLIT)
pc-$(CLASS_SPLIT):
make -C BUILDENV CLASS=$(CLASS_SPLIT) BUILD=just_filelist OUTPUT=filelist.txt SKIPSELF=yes
../classCvt/classCvt <./Applications/$(CLASS_SPLIT).class> ./Applications/$(CLASS_SPLIT).ujc
time -p ./$(APP) ./Applications/$(CLASS_SPLIT).ujc `cat filelist.txt`
endef
CLASSES := $(shell echo $(CLASS) | tr ',' ' ')
$(foreach CLASS_SPLIT, $(CLASSES), $(eval $(BUILD_CLASS)))
Simple way:
pc: $(APP)
$(foreach C, $(shell echo $(CLASS) | tr ',' ' '), \
make -C BUILDENV CLASS=$(C) BUILD=just_filelist OUTPUT=filelist.txt SKIPSELF=yes && \
../classCvt/classCvt <./Applications/$(C).class> ./Applications/$(C).ujc && \
time -p ./$(APP) ./Applications/$(C).ujc `cat filelist.txt` &&) true

Bash , append line at the end of each file

I have files in a dir.
I need to append a new line and the file name at the end of each file.
This should do:
for f in *; do echo >> $f; echo $f >> $f; done
First echo a new-line, then echo the filename.
The >> says "append at the end of the file".
Edit: aioobe's answer has been updated to show the -e flag that I didn't see when I first answered this. Thus I'm now just showing an example which includes a directory and how to eliminate the directory name:
#!/bin/bash
for fn in dir/*
do
shortname=${fn/#*\//}
echo -e "\n$shortname" >> $fn
done
If you want the directory name, take out the shortname=${fn/#*\//} line and replace $shortname with $fn in the echo.
Let xargs do the looping:
# recursive, includes directory name
find -type f -print0 | xargs -0 -I% bash -c 'echo -e "\n%" >> %'
or
# non-recursive, doesn't include directory name
find -maxdepth 1 -type f -exec basename {} \; | xargs -I% bash -c 'echo -e "\n%" >> %'
or
# non-recursive, doesn't include directory name
find -maxdepth 1 -type f -printf "%f\0" | xargs -0 -I% bash -c 'echo -e "\n%" >> %'
or
# recursive, doesn't include directory name
find -type f -print0 | xargs -0 -I% bash -c 'f=%; echo -e "\n${f##*/}" >> %'
Another method using ex (or vim) :
ex -c 'args **/*' -c 'set hidden' -c 'argdo $put =bufname(".")' -c 'wqa'

Resources