Find and basename not playing nicely
The trouble with your original attempt:
find www/*.html -type f -exec sh -c "echo $(basename {})" \;
is that the $(basename {})
code is executed once, before the find
command is executed. The output of the single basename
is {}
since that is the basename of {}
as a filename. So, the command that is executed by find is:
sh -c "echo {}"
for each file found, but find
actually substitutes the original (unmodified) file name each time because the {}
characters appear in the string to be executed.
If you wanted it to work, you could use single quotes instead of double quotes:
find www/*.html -type f -exec sh -c 'echo $(basename {})' \;
However, making echo
repeat to standard output what basename
would have written to standard output anyway is a little pointless:
find www/*.html -type f -exec sh -c 'basename {}' \;
and we can reduce that still further, of course, to:
find www/*.html -type f -exec basename {} \;
Could you also explain the difference between single quotes and double quotes here?
This is routine shell behaviour. Let's take a slightly different command (but only slightly — the names of the files could be anywhere under the www
directory, not just one level down), and look at the single-quote (SQ) and double-quote (DQ) versions of the command:
find www -name '*.html' -type f -exec sh -c "echo $(basename {})" \; # DQ
find www -name '*.html' -type f -exec sh -c 'echo $(basename {})' \; # SQ
The single quotes pass the material enclosed direct to the command. Thus, in the SQ command line, the shell that launches find
removes the enclosing quotes and the find
command sees its $9
argument as:
echo $(basename {})
because the shell removes the quotes. By comparison, the material in the double quotes is processed by the shell. Thus, in the DQ command line, the shell (that launches find
— not the one launched by find
) sees the $(basename {})
part of the string and executes it, getting back {}
, so the string it passes to find
as its $9
argument is:
echo {}
Now, when find
does its -exec
action, in both cases it replaces the {}
by the filename that it just found (for sake of argument, www/pics/index.html
). Thus, you get two different commands being executed:
sh -c 'echo $(basename www/pics/index.html)' # SQ
sh -c "echo www/pics/index.html" # DQ
There's a (slight) notational cheat going on there — those are the equivalent commands that you'd type at the shell. The $2
of the shell that is launched actually has no quotes in it in either case — the launched shell does not see any quotes.
As you can see, the DQ command simply echoes the file name; the SQ command runs the basename
command and captures its output, and then echoes the captured output. A little bit of reductionist thinking shows that the DQ command could be written as -print
instead of using -exec
, and the SQ command could be written as -exec basename {} \;
.
If you're using GNU find
, it supports the -printf
action which can be followed by Format Directives such that running basename
is unnecessary. However, that is only available in GNU find
; the rest of the discussion here applies to any version of find
you're likely to encounter.
Using basename to name output file in java
Here's a zsh approach, since it's tagged it as such.
for f in **/*.foo(.); print -- java ... -o $f:r.bar $f
Remove the print --
when you're satisfied that it looks good.
The (.)
says files only. The :r
says remove the .foo
extension. It's helpful to remember the path manipulators as "erth" for extension/remove/tail/head.
There's also zmv
with the -p
option to call your java command.
Why doesn't basename execute in find?
$(basename {})
is evaluated before find
is even started: you tell the shell to use the result of basename {}
to construct find
command line. So basically it does:
$(basename {})
evaluates to{}
- Calls
find -name "*.c" -exec echo {} \;
find
constructs all-exec
commands; eg.echo /path/to/file
That's why you have only the whole path.
To achieve what you want, here is an example:
find . -name "*.c" -exec basename {} \;
Note that I added the starting path to find
, otherwise it's not portable.
UPDATE: This answer shows an example from @chepner in a comment below. My previous example was way too complicated for no reason.
basename command not working as expected
The basename
command has two uses, stripping path prefixes and (optionally) stripping suffixes.
To strip a path prefix, you can simply pass it a path, for example
basename /path/to/file.ext
# file.ext
To additionally strip a suffix, you need to tell basename
the suffix you wish to strip, for example
basename /path/to/file.ext .ext
# file
basename DSCN2612.JPG .JPG
# DSCN2612
So, basename
won't "auto-detect" a suffix because in unix, files don't necessarily have suffixes. In other words, letters after a period aren't necessarily suffixes, so you need to explicitly tell it what suffix to strip.
There are some bash-specific alternatives to "auto-detect" and strip, however. For example,
x="file.ext"
echo ${x%.*}
# file
Without knowing more, I might write your script as
for jpg in *.JPG; do
base=${jpg%.*}
convert "$jpg" "$base.pdf"
done
Running basename on remote server with xargs does not produce expected results
You need to escape the $
so that it will be sent literally to the remote machine. Otherwise, $(basename {})
is executed locally, and the output {}
is substituted into the ssh
argument.
ssh -q remote-host "find ~/test/directory -type f -iname '*myFile*' | xargs -I{} sh -c '{ echo {}; echo \$(basename {}); }'"
BASH dirname basename problems with spaces
Quote your $file
variable in every place where is used:
find $path1 -iname "*jpeg" | \
while read file;
do
if [ -u "${id}" ]; then
filebase=`basename "$file" .jpeg`
dirbase=`dirname "$file"`
fi
done
Related Topics
Why Docker Has Ability to Run Different Linux Distribution
What Would Be the Equivalent of Win32 API in Linux
Linux-Based Firmware, How to Implement a Good Way to Update
Sed - Pass Match to External Command
How to Properly Set Java_Home in /Etc/Environment
How to Wake Select() on a Socket Close
How to Check The Hit Count for Each Rule in Iptables
Append The Time Stamp to a File Name in Ubuntu
Linux Command (Like Cat) to Read a Specified Quantity of Characters
Expression After Last Specific Character
Hard Time in Understanding Module_Device_Table(Usb, Id_Table) Usage
Find and Replace a Particular Term in Multiple Files
Android Sdk on a 64-Bit Linux MAChine
Check If Vt-D/Iommu Has Been Enabled in The Bios/Uefi
Error While Installing Dbd::Oracle
All Newlines Are Removed When Saving Cat Output into a Variable