This guide demonstrates how to programmatically duplicate files with new names based on filename patterns using Zsh scripting.
Common use case: You're working with a codebase using .tsx
files (React TypeScript) and need to quickly scaffold variants of each component β like *-skeleton.tsx
and *-fallback.tsx
files.
For every *.tsx
file in a directory (e.g. some-directory
), create one or more new files with a modified name, excluding certain patterns to avoid unnecessary duplication.
some-directory/
βββ A.tsx
βββ B.tsx
βββ C.tsx
βββ D.tsx
Each .tsx
file should have a corresponding *-skeleton.tsx
file:
some-directory/
βββ A.tsx
βββ A-skeleton.tsx
βββ B.tsx
βββ B-skeleton.tsx
βββ C.tsx
βββ C-skeleton.tsx
βββ D.tsx
βββ D-skeleton.tsx
for f in *.tsx(.N); do
[[ $f != *-skeleton.tsx ]] && touch "${f%.tsx}-skeleton.tsx"
done
for f in *.tsx(.N); do [[ $f != *-skeleton.tsx ]] && touch "${f%.tsx}-skeleton.tsx"; done\n
-
for f in *.tsx(.N)
-
Iterates over all
.tsx
files in the current directory. -
(.N)
is a Zsh glob qualifier:.
: plain (non-directory) files only.N
: nullglob; prevents errors if no matches are found.
-
-
[[ $f != *-skeleton.tsx ]]
- Skips files that already have
-skeleton
in the filename.
- Skips files that already have
-
touch "${f%.tsx}-skeleton.tsx"
- Creates an empty new file with
-skeleton.tsx
appended, preserving the original name without.tsx
.
- Creates an empty new file with
Now suppose you already have *-skeleton.tsx
files and want to also create *-fallback.tsx
versions, but only for the original files (i.e. not for files that are already -skeleton.tsx
or -fallback.tsx
).
some-directory/
βββ A.tsx
βββ A-skeleton.tsx
βββ B.tsx
βββ B-skeleton.tsx
βββ C.tsx
βββ C-skeleton.tsx
βββ D.tsx
βββ D-skeleton.tsx
some-directory/
βββ A.tsx
βββ A-skeleton.tsx
βββ A-fallback.tsx
βββ B.tsx
βββ B-skeleton.tsx
βββ B-fallback.tsx
βββ C.tsx
βββ C-skeleton.tsx
βββ C-fallback.tsx
βββ D.tsx
βββ D-skeleton.tsx
βββ D-fallback.tsx
for f in *.tsx(.N); do
[[ $f != *-skeleton.tsx && $f != *-fallback.tsx ]] && touch "${f%.tsx}-fallback.tsx"
done
for f in *.tsx(.N); do [[ $f != *-skeleton.tsx && $f != *-fallback.tsx ]] && touch "${f%.tsx}-fallback.tsx"; done\n
-
Adds a second conditional:
[[ $f != *-skeleton.tsx && $f != *-fallback.tsx ]]
- Ensures you're only acting on original
*.tsx
files, not previously generated skeleton or fallback versions.
- Ensures you're only acting on original
-
Creates
*-fallback.tsx
files using the same basename as the source file.
- These examples assume you are running the command inside the
some-directory
folder. - You can adjust the script to run recursively or across multiple folders with additional scripting logic (e.g.,
find
,zargs
, or custom scripts). - Ensure you're using Zsh, not Bash, since glob qualifiers like
(.N)
are specific to Zsh.
# Create -skeleton.tsx files
for f in *.tsx(.N); do
[[ $f != *-skeleton.tsx ]] && touch "${f%.tsx}-skeleton.tsx"
done
# Create -fallback.tsx files
for f in *.tsx(.N); do
[[ $f != *-skeleton.tsx && $f != *-fallback.tsx ]] && touch "${f%.tsx}-fallback.tsx"
done
Instead of creating empty files, this variation copies the original contents of each .tsx
file into its -skeleton.tsx
and/or -fallback.tsx
counterparts.
# Create -skeleton.tsx files with copied content
for f in *.tsx(.N); do
[[ $f != *-skeleton.tsx ]] && cp "$f" "${f%.tsx}-skeleton.tsx"
done
# Create -fallback.tsx files with copied content
for f in *.tsx(.N); do
[[ $f != *-skeleton.tsx && $f != *-fallback.tsx ]] && cp "$f" "${f%.tsx}-fallback.tsx"
done
cp "$f" "${f%.tsx}-skeleton.tsx"
: Usescp
instead oftouch
to copy the file contents into the new filename.- Maintains the same logic to skip already existing skeleton or fallback files.
In this advanced case, the duplication should occur recursively, affecting all .tsx
files in some-directory
and its subfolders.
This version uses find
and zsh
-compatible substitution to replicate the previous behaviour across a nested structure.
find some-directory -type f -name '*.tsx' ! -name '*-skeleton.tsx' -exec sh -c 'for f; do touch "${f%.tsx}-skeleton.tsx"; done' _ {} +
find some-directory -type f -name '*.tsx' ! -name '*-skeleton.tsx' ! -name '*-fallback.tsx' -exec sh -c 'for f; do touch "${f%.tsx}-fallback.tsx"; done' _ {} +
find some-directory -type f -name '*.tsx' ! -name '*-skeleton.tsx' -exec sh -c 'for f; do cp "$f" "${f%.tsx}-skeleton.tsx"; done' _ {} +
find some-directory -type f -name '*.tsx' ! -name '*-skeleton.tsx' ! -name '*-fallback.tsx' -exec sh -c 'for f; do cp "$f" "${f%.tsx}-fallback.tsx"; done' _ {} +
find some-directory -type f -name '*.tsx'
: Recursively finds.tsx
files.! -name '*-skeleton.tsx'
: Excludes skeleton files.-exec sh -c '...' _ {} +
: Batches the matched files into a mini shell where you loop over them and apply the copy or touch logic.