Gzip CSS/JS Using OS X Terminal - macos

I am trying to use OSX Terminal to create gzip versions (.gz) of all css & js files in a folder. I found the following command, but when I cd to a test folder & then enter the command, it doesn't output anything & I would expect it to create a gzip copy in the folder next to the original file:
find . -regex ".*\(css\|js\)$" -exec bash -c 'echo Compressing "{}" && gzip -c --best "{}" > "{}.gz"' \;
What am I doing wrong? Or does the command need to be modified?

It seems that OS X version of find doesn't support extended regular expressions. Simple workaround would be to use logical or operator (-o option) like this:
find . \( -name "*\.js" -o -name "*\.css" \) -exec bash -c 'echo Compressing "{}" && gzip -c --best "{}" > "{}.gz"' \;
It will search for both file extensions and exec bash command for each found file.
Update.
I actually found out that your syntax will also work. You need to use -E option of find command to work with extended regular expressions. You may also need to enclose the pattern in double quotes.
find -E . -regex "".*\(css\|js\)$"" -exec bash -c 'echo Compressing "{}" && gzip -c --best "{}" > "{}.gz"' \;
From find man page:
-E Interpret regular expressions followed by -regex and -iregex
primaries as extended (modern) regular expressions rather than basic
regular expressions (BRE's). The re_format(7) manual page fully
describes both formats.

Related

Wildcard within if conditional of Bash fails to execute. Works when literal filename provided

I am trying to execute a command depending on the file type within directory. But am unable to check the content within directory using wildcard. When provided a literal filename I am able to execute.
find ./* -type d -execdir bash -c 'DIR=$(basename {}); if [[ -e {}/*.png ]]; then echo "img2pdf {}/*.png -o $DIR.pdf"; fi ' \;
Instead of going over directories, and then looking for png-s inside, find can find png-s straight away:
find . -name '*.png'
Then you can process it as you do, or using xargs:
find . -name '*.png' | xargs -I '{}' img2pdf '{}' -o '{}.pdf'
The command above will process convert each png to a separate pdf.
If you want to pass all png-s at once, and call img2pdf once:
find . -name '*.png' | xargs img2pdf -o out.pdf

Delete the contents of all files in BASH recursively? [duplicate]

I would like to clear the content of many log files of a given directory recursively, without deleting every file. Is that possible with a simple command?
I know that I can do > logs/logfile.log one by one, but there are lots of logs in that folder, and that is not straightforward.
I am using macOS Sierra by the way.
Thanks to #chepner for showing me the better way to protect against double quotes in the file names:
You can use find to do it
find start_dir -type f -exec sh -c '> "$1"' _ {} \;
And you could add extra restrictions if you don't want all files, like if you want only files ending in .log you could do
find start_dir -type f -name '*.log' -exec sh -c '> "$1"' _ {} \;
As macOS includes Perl anyway:
perl -e 'for(<logs/*log>){truncate $_,0}'
Or, more succinctly, if you use homebrew and you have installed GNU Parallel (which is just a Perl script), you can do:
parallel '>' ::: logs/*log

I used the'-print0' option to handle filenames with spaces, but I get an error

#!/bin/bash
find /home/data -name '*QQ*' -print0 -exec bash -c ' mv $1 ${0/\-QQ/-TT}' {} \;
I used the'-print0' option to handle filenames with spaces, but I get an error
/home/data/gone to sea.1080p-QQ.mp4mv: target 'sea.1080p-TT.mp4' is not a directory
Which part is wrong?
Thanks
You don't need -print0, since you're not piping the output to another program.
You just need to quote properly in the bash command.
find /home/data -name '*-QQ*' -exec bash -c 'mv "$1" "${1/\-QQ/-TT}"' {} {} \;
This should work as long as the filenames don't contain double quote or $ characters.
You could also avoid bash -c by using the rename command:
find /home/data -name '*-QQ*' -exec rename 's/-QQ/-TT/' {} +

batch rename file extensions in subdirectories

I'm trying to create a batch file in linux that will allow me to change extensions of files in multiple subdirectories. After much searching and experimenting i've found what seems to be a solution:
find /volume1/uploads -name "*.mkv" -exec rename .mkv .avi {} +
When running the script i get the following error:
find: -exec CMD must end by ';'
I've tried adding ; and \; (with or without +) but to no avail. What's wrong with the command and how can I fix it?
Edit: Running on a Synology NAS with DSM 4.2
you have to escape all characters that would be interpreted by bash. in your case these are the semicolon and the curly braces (you forgot to escape the latter in your code):
find /volume1/uploads -name "*.mkv" -exec rename .mkv .avi \{\} \;
the {} (in our case \{\}) is expanded to the filename, so the actual call would look like rename .mkv .avi /volume1/uploads/foo/bla.mkv (which is not the exact syntax the /usr/bin/rename needs, at least on my system).
instead it would be something like:
find /volume1/uploads -name "*.mkv" -exec rename 's/\.mkv$/.avi/' \{\} \;
UPDATE
if you don't want to (or cannot) use perl's rename script, you could use the following simple bash script and save it as /tmp/rename.sh
#!/bin/sh
INFILE=$1
OUTFILE="${INFILE%.mkv}.avi"
echo "moving ${INFILE} to ${OUTFILE}"
mv "${INFILE}" "${OUTFILE}"
make it executable (chmod u+x /tmp/rename.sh) and call:
find /volume1/uploads -name "*.mkv" -exec /tmp/rename.sh \{\} \;
UPDATE2
it turned out that this question is really not about bash but about busybox.
with a limited shell interpreter like busybox, the simplest solution is just to append the new file extension:
find /volume1/uploads -name "*.mkv" -exec mv \{\} \{\}.avi \;
Not sure how different find and rename commands are on your DSM 4.2 OS so try something like:
find /volume1/uploads -name "*.mkv" | while read filename;
do mv -v "${filename}" "$(echo "${filename}" | sed -e 's/\.mkv$/\.avi/')"
done

Repeated input redirection to c++ executable in bash

I have written an executable in c++, which is designed to take input from a file, and output to stdout (which I would like to redirect to a single file). The issue is, I want to run this on all of the files in a folder, and the find command that I am using is not cooperating. The command that I am using is:
find -name files/* -exec ./stagger < {} \;
From looking at examples, it is my understanding that {} replaces the file name. However, I am getting the error:
-bash: {}: No such file or directory
I am assuming that once this is ironed out, in order to get all of the results into one file, I could simply use the pattern Command >> outputfile.txt.
Thank you for any help, and let me know if the question can be clarified.
The problem that you are having is that redirection is processed before the find command. You can work around this by spawning another bash process in the -exec call:
find files/* -exec bash -c '/path/to/stagger < "$1"' -- {} \;
The < operator is interpreted as a redirect by the shell prior to running the command. The shell tries redirecting input from a file named {} to find's stdin, and an error occurs if the file doesn't exist.
The argument to -name is unquoted and contains a glob character. The shell applies pathname expansion and gives nonsensical arguments to find.
Filenames can't contain slashes. The argument to -name can't work even if it were quoted. If GNU find is available, -path can be used to specify a glob pattern files/*, but this doesn't mean "files in directories named files", for that you need -regex. Portable solutions are harder.
You need to specify one or more paths for find to start from.
Assuming what you really wanted was to have a shell perform the redirect, Here's a way with GNU find.
find . -type f -regex '.*foo/[^/]*$' -exec sh -c 'for x; do ./stagger <"$x"; done' -- {} +
This is probably the best portable way using find (-depth and -prune won't work for this):
find . -type d -name files -exec sh -c 'for x; do for y in "$x"/*; do [ -f "$y" ] && ./stagger <"$y"; done; done' -- {} +
If you're using Bash, this problem is a very good candidate for just using a globstar pattern instead of find.
#!/usr/bin/env bash
shopt -s extglob globstar nullglob
for x in **/files/*; do
[[ -f "$x" ]] && ./stagger <"$x"
done
Simply escape the less-than symbol, so that redirection is carried out by the find command rather than the shell it is running in:
find files/* -exec ./stagger \< {} \;

Resources