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

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"

Related

moving files to their respective folders using bash scripting

I have files in this format:
2022-03-5344-REQUEST.jpg
2022-03-5344-IMAGE.jpg
2022-03-5344-00imgtest.jpg
2022-03-5344-anotherone.JPG
2022-03-5343-kdijffj.JPG
2022-03-5343-zslkjfs.jpg
2022-03-5343-myimage-2010.jpg
2022-03-5343-anotherone.png
2022-03-5342-ebee5654.jpeg
2022-03-5342-dec.jpg
2022-03-5341-att.jpg
2022-03-5341-timephoto_december.jpeg
....
about 13k images like these.
I want to create folders like:
2022-03-5344/
2022-03-5343/
2022-03-5342/
2022-03-5341/
....
I started manually moving them like:
mkdir name
mv name-* name/
But of course I'm not gonna repeat this process for 13k files.
So I want to do this using bash scripting, and since I am new to bash, and I am working on a production environment, I want to play it safe, but it doesn't give me my results. This is what I did so far:
#!/bin/bash
name = $1
mkdir "$name"
mv "${name}-*" $name/
and all I can do is: ./move.sh name for every folder, I didn't know how to automate this using loops.
With bash and a regex. I assume that the files are all in the current directory.
for name in *; do
if [[ "$name" =~ (^....-..-....)- ]]; then
dir="${BASH_REMATCH[1]}"; # dir contains 2022-03-5344, e.g.
echo mkdir -p "$dir" || exit 1;
echo mv -v "$name" "$dir";
fi;
done
If output looks okay, remove both echo.
Try this
xargs -i sh -c 'mkdir -p {}; mv {}-* {}' < <(ls *-*-*-*|awk -F- -vOFS=- '{print $1,$2,$3}'|uniq)
Or:
find . -maxdepth 1 -type f -name "*-*-*-*" | \
awk -F- -vOFS=- '{print $1,$2,$3}' | \
sort -u | \
xargs -i sh -c 'mkdir -p {}; mv {}-* {}'
Or find with regex:
find . -maxdepth 1 -type f -regextype posix-extended -regex ".*/[0-9]{4}-[0-9]{2}-[0-9]{4}.*"
You could use awk
$ cat awk.script
/^[[:digit:]-]/ && ! a[$1]++ {
dir=$1
} /^[[:digit:]-]/ {
system("sudo mkdir " dir )
system("sudo mv " $0" "dir"/"$0)
}
To call the script and use for your purposes;
$ awk -F"-([0-9]+)?[[:alpha:]]+.*" -f awk.script <(ls)
You will see some errors such as;
mkdir: cannot create directory ‘2022-03-5341’: File exists
after the initial dir has been created, you can safely ignore these as the dir now exist.
The content of each directory will now have the relevant files
$ ls 2022-03-5344
2022-03-5344-00imgtest.jpg 2022-03-5344-IMAGE.jpg 2022-03-5344-REQUEST.jpg 2022-03-5344-anotherone.JPG

xargs argument not interpreted

I have a directory $dir that contains .txt.xy files and subdirectories with .txt.xy files. I try to iterate over each file and pass the whole path as well as the path without $dir as argument to a program like this:
dir="/path/to/"
suffix=".xy"
find "$dir" -name "*.txt.xy" -print0 | xargs -0 -I {} sh -c 'program "$1" |
subprogram filename="$2"' _ {} "$(echo {} | sed -e "s#^${dir}##" -e "s#${suffix}\$##")"
$1 shold be the full path (e.g. /path/to/subdir/file.txt.xy)
$2 should be the full path without $dir and $suffix (e.g. subdir/file.txt)
$1 is the propper full path but $2 is also the full path as if the pipe in $(...) is never executed. What am I missing here?
Your attempt seems rather roundabout. It sounds like you are looking for
find /path/to -name "*.txt.xy" -exec sh -c '
for f; do
g=${f##*/}
program "$f" | subprogram filename="${g%.xy}"
done' _ {} +
If you really need your parameters to be in variables, maybe pass in the suffix as $0 which isn't used for anything useful here anyway. It's a bit obscure, but helps avoid the mess you had with double quotes.
find /path/to -name "*.txt.xy" -exec sh -c '
for f; do
g=${f##*/}
program "$f" | subprogram filename="${g%"$0"}"
done' ".xy" {} +
The above simply trims g to the basename which I guess on closer reading you don't want. So pass /path/to in $0 instead, and hardcode .xy inside:
find /path/to -name "*.txt.xy" -exec sh -c '
for f; do
g=${f#"$0"/}
program "$f" | subprogram filename="${g%.xy}"
done' "/path/to" {} +
or if you really need both to be command-line parameters,
dir="/path/to"
suffix=".xy"
find "$dir" -name "*.txt$suffix" -exec sh -c '
suffix=$1
shift
for f; do
g=${f#"$0"/}
program "$f" | subprogram filename="${g%"$suffix"}"
done' "$dir" "$suffix" {} +
One reason for the failure, is that the command substitution with xargs under double quotes is expanded by the shell even before the former is executed. One way to avoid that would be to do the whole substitution inside the sub-shell created by sh -c as
find "$dir" -name "*.txt.xy" -print0 |
xargs -0 -I {} sh -c '
f="{}"
g="$(echo "$f" | sed -e 's|"'^"${dir}"'"||' -e 's|"'\\"${suffix}"$'"||' )"
program "$f" | subprogram filename="$g"
'

Passing a date/time value to touch command within bash function

I am trying to write a bash function (Mac OS X) which searches for a specific range of files between two input date/times. Most variations I have tried for evaluating the two inputs $1 and $2 fail. Hardcoding the times works fine (as per usage line below) i.e. the search syntax is fine. Grateful for pointers where I'm going wrong on passing the two inputs to the touch commands.
function ffiles_search1 () {
echo "usage start 201911270000 end 201912102359 "
touch -t $(eval echo "$1") /tmp/lower-date && touch -t $(eval echo "$2") /tmp/upper-date && find . -path "./Library" -prune -o -type f -a -newer /tmp/lower-date -a ! -newer /tmp/upper-date -a -size +32k -a ! -size +1024k -print0 | xargs -0 ls -ld | egrep -iv "|ppt|doc"
}
Corrected code, invoking as
ffiles_search 201911270000 201912102359
on the function
function ffiles_search () {
echo "usage start 201911270000 end 201912102359 "
touch -t "$1" /tmp/lower-date &&
touch -t "$2" /tmp/upper-date &&
find . -path "./Library" -prune -o -type f -a -newer /tmp/lower-date \
-a ! -newer /tmp/upper-date -a -size +32k -a -size -1024k -print0 |
xargs -0 ls -ld |
egrep -iv -f $HOME/Scripts/egrep_exclusions/time_search.txt
}

Bash conditional with shell variable

I want to check if a file exists with [ -f "$1" ] but it's not working. The command is working with plain text like [ -f "filename.xml" ].
I echoed $1 which is for example filename.xml. Any ideas?
sourcePath=/SPECIFICPATH/${1};
echo $sourcePath;
echo $1;
find /EXAMPLEPATH -name pages -type d -execdir bash -c 'cd pages && [ -f "$1" ] && pwd && cp $sourcePath .' \;
I'm working in automator using a shell script block.
You’re invoking an entirely new shell with bash -c …, so you need to pass $1 along. Same with $sourcePath, if it’s not exported.
find /EXAMPLEPATH -name pages -type d -execdir bash -c 'cd pages && [ -f "$1" ] && pwd && cp "$2" .' bash "$1" "$sourcePath" \;
(In bash -c … bash "$1" "$sourcePath", the second bash is $0.)
There's no need for the subshell if you move some of the logic to the find command.
find /EXAMPLEPATH -wholename "*/pages/$1" -print -execdir cp "$sourcePath" . \;
-wholename matches a file named $1 in a pages directory.
-print replaces pwd.
Now you can call cp directly.

Execute command in all immediate subdirectories

I'm trying to add a shell function (zsh) mexec to execute the same command in all immediate subdirectories e.g. with the following structure
~
-- folder1
-- folder2
mexec pwd would show for example
/home/me/folder1
/home/me/folder2
I'm using find to pull the immediate subdirectories. The problem is getting the passed in command to execute. Here's my first function defintion:
mexec() {
find . -mindepth 1 -maxdepth 1 -type d | xargs -I'{}' \
/bin/zsh -c "cd {} && $#;";
}
only executes the command itself but doesn't pass in the arguments i.e. mexec ls -al behaves exactly like ls
Changing the second line to /bin/zsh -c "(cd {} && $#);", mexec works for just mexec ls but shows this error for mexec ls -al:
zsh:1: parse error near `ls'
Going the exec route with find
find . -mindepth 1 -maxdepth 1 -type d -exec /bin/zsh -c "(cd {} && $#)" \;
Gives me the same thing which leads me to believe there's a problem with how I'm passing the arguments to zsh. This also seems to be a problem if I use bash: the error shown is:
-a);: -c: line 1: syntax error: unexpected end of file
What would be a good way to achieve this?
Can you try using this simple loop which loops in all sub-directories at one level deep and execute commands on it,
for d in ./*/ ; do (cd "$d" && ls -al); done
(cmd1 && cmd2) opens a sub-shell to run the commands. Since it is a child shell, the parent shell (the shell from which you're running this command) retains its current folder and other environment variables.
Wrap it around in a function in a proper zsh script as
#!/bin/zsh
function runCommand() {
for d in ./*/ ; do /bin/zsh -c "(cd "$d" && "$#")"; done
}
runCommand "ls -al"
should work just fine for you.
#!/bin/zsh
# A simple script with a function...
mexec()
{
export THE_COMMAND=$#
find . -type d -maxdepth 1 -mindepth 1 -print0 | xargs -0 -I{} zsh -c 'cd "{}" && echo "{}" && echo "$('$THE_COMMAND')" && echo -e'
}
mexec ls -al
using https://github.com/sharkdp/fd but you could as well use plain old find instead of fdfind
function inDirs() { fdfind --type d --max-depth 1 --exec bash -c "x={} && echo && echo \$x && echo \${x//?/=} && cd {} && echo '-> '$* && $*" ; }

Resources