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"
donefor f in *.tsx(.N); do [[ $f != *-skeleton.tsx ]] && touch "${f%.tsx}-skeleton.tsx"; done\n-
for f in *.tsx(.N)-
Iterates over all
.tsxfiles 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
-skeletonin the filename.
- Skips files that already have
-
touch "${f%.tsx}-skeleton.tsx"- Creates an empty new file with
-skeleton.tsxappended, 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"
donefor 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
*.tsxfiles, not previously generated skeleton or fallback versions.
- Ensures you're only acting on original
-
Creates
*-fallback.tsxfiles using the same basename as the source file.
- These examples assume you are running the command inside the
some-directoryfolder. - 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"
doneInstead 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"
donecp "$f" "${f%.tsx}-skeleton.tsx": Usescpinstead oftouchto 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.tsxfiles.! -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.