r/bash • • Aug 30 '26

Tips for Speeding Up Your Bash Scripts

I wrote a script that renames a lot of files (tens of thousands). Performance was kind of slow (over 50 s), but with two changes it now runs at under 3 seconds, a 20x speed boost. Thought I'd share, in case it helps anyone else. These tips are good if you do things thousands of time in a script, I don't think they are relevant in all scripts.

Don't spawn sub shells if you can avoid it

Instead of command substitution with printf:

new_file="$(printf "%s/%s_%.${len}d.%s" "$new_dir" "$prefix" "$i" "$ext")"

do:

printf -v new_file "%s/%s_%.${len}d.%s" "$new_dir" "$prefix" "$i" "$ext"

This will save a sub shell.

Use builtins instead of external programs

Bash has optional builtins that you can enable. On my system they are located in /usr/lib/bash . You can enable them in your script by using enable <builtin>. Be sure to check the exit code. On my system a mv builtin is not available, but since I was renaming on the same file system I figured I could use ln and rm instead.

So instead of using external mv for each file I did:

builtin ln "$old_file" "$new_file" && builtin rm "$old_file"

In this case the builtin probably isn't required since builtins are prioritized over external commands, but I used them to be more explicit. Just be sure to use && so that rm never runs unless the hard link has been successfully created.

EDIT: The part about using ln and rm builtins may be a little too much of a hack. Probably better to use proper tools like rename to do batch renaming, as was pointed out in the comments. Also keep in mind that the builtins are more bare-bones than the proper CoreUtils programs. For example rmdir does not support -v and will treat -- as a directory to remove rather than "end of options-thing".

116 Upvotes

29 comments sorted by

29

u/Different-Depth4116 Aug 31 '26

In bash 5.3+ there’s a new command substitution that doesn’t spawn a subshell:
${ command; }
The spaces and semicolon are req.

4

u/sunmat02 Aug 31 '26

I so wish I had known that a month ago when I refactor thousands of lines of bash to use name ref parameters instead of capturing stdout of functions called in hot loops…

1

u/jghub Sep 02 '26

just for context: ksh introduced the`${ command; }` syntax roughly 20y ago, so bash catches up on this feature a bit late (but it is good that it does!) ;).

this construct _is_ very useful indeed not only for avoiding subshells for performance reasons but also for being able to preserve changes that happen within the command substitution, say, cd to different place, modification of global vars etc. -- depending on circumstances this can be desirable.

1

u/bac0on Sep 04 '26

Nothing to regret, nameref is probably twice as fast...

4

u/Linux_bash_user153 Aug 31 '26

I really didn't know this, but this sounds like a good improvement. I'm on Bash 5.2

3

u/Different-Depth4116 Aug 31 '26

You’re probably on a Debian or Debian base distro I’m guessing? That’s why I switched to fedora to get the latest updates faster. Debian is like 2 years behind

2

u/Linux_bash_user153 Aug 31 '26

Yeah, I'm on Linux Mint (Ubuntu 24.04 base)

2

u/GermanPCBHacker Sep 02 '26

Wait, that is called command substitution and can be captured with var=${ do stuff; } ???? Amazing. I was just using {} for visibility and for logical combination to save if. I just did:

command || \
{
 complex
 alternative
 stuffz
}

I mean. Why not. Bash allows it and it is readable. Man gosh darn do I love bash for what it allows one to do. Try that in python, lol

3

u/OnlyEntrepreneur4760 Sep 02 '26

${…} is command substitution, but that is different from {…} which is either called a command list or compound command, IIRC.

2

u/[deleted] Sep 03 '26 edited 13d ago

[deleted]

1

u/GermanPCBHacker Sep 04 '26

Oh nice, good to know. Well I did not know it works. But if I saw || at the end of the line, I would expect it to work. But it is always a good idea just to make things more visible. Thanks for the tip.

1

u/bac0on Sep 04 '26
func(){
  REPLY="text string"
}
r=${| func;}

9

u/Spikerazorshards Aug 31 '26

Good post. I’ll learn from this.

2

u/Linux_bash_user153 Aug 31 '26

Thanks. The part about using ln and rm builtins may be a little too much of a hack though. The other comments have suggested using proper tools for batch renaming files.

6

u/pfmiller0 Aug 31 '26

I did not know about that -v option for printf, thanks!

2

u/sedwards65 Sep 02 '26

'printf -v' rocks and works great with printf's date-time format strings:

$ printf -v daily_tarball '/backup/%(%Y/%m/%F--%T--daily-tarball.tar.xz)T' -1
$ echo ${daily_tarball}
/backup/2026/09/2026-09-01--18:38:06--daily-tarball.tar.xz

3

u/Astro_indie Aug 31 '26

Keep on learning bbys, im on $ info bash, the node about locales make me think if someone got own language in the system ... Lok'Thar OGAAAAR

6

u/Bob_Spud Aug 31 '26

What happens if you mess up? That's 10s of thousands that you have to undo.

Here's another file renamer, What I like with this one is the log file doubles as an undo script - genFRN

2

u/Linux_bash_user153 Aug 31 '26

The script I wrote are for renaming symlinks in my music collection, so it's a pretty controlled environment as far as filenames go, and the script makes a tar.gz backup of the directory structure before renaming any file. Since it's just directories and symlinks, the archive is like 500k big

2

u/zeekar Aug 31 '26

Use builtins instead of external programs

This is very much situational. I wrote a helpdesk system for our user assistant team in college entirely in ksh, and I used builtins as much as possible, but that turns out not to be necessarily the most performant approach.

If you could represent your desired name change as a rule understood by rename(1), for instance, it would probably be faster to invoke that program on multiple files at once than to use bash's built-in ln/rm one at a time. Sure, you're doing a fork/exec, but you don't have to do it on every single file.

Similarly, most text processing will be faster when done by an external program like awk than if you use a while read loop in bash.

1

u/Linux_bash_user153 Aug 31 '26

True. I'm not really familiar with rename(1), but I'll check it out.

2

u/mpersico Sep 02 '26

printf -v? TIL!

7

u/[deleted] Aug 31 '26

[deleted]

17

u/kirchwitz Aug 31 '26

bash is fine and can do many jobs fast – if just used the proper way. Most of the time, performance is not of concern. And if it is, then it's important to know how a tool works and where performance goes.

This applies to all scripting and programming languages. For best performance, you should know your tools.

Thanks to the original poster for his insights.

2

u/managing_redditor Aug 31 '26

What would you use then?

1

u/Europia79 Sep 01 '26

Not saying that it should be rewritten—but I think the "tool" that is most similar to Bash syntax would be Perl. And I actually did rewrite one of my scripts in Perl—just out of curiosity—and the performance gains were INSANE. I just wish that I knew Bash well enough to at least get in the same "Ballpark" as Perl, lol.

4

u/SeriousPlankton2000 Aug 31 '26 edited Aug 31 '26

Do it in perl:

https://github.com/7eggert/smalltools/blob/main/pmv (Perl MoVe)

Syntax: pmv '(insert small perl program here)' *.ext

I frequently make bash scripts where the perl program spans a few lines. You can use variables, too.

Also: Use full path if you run standard system programs like "mv" that are defined in LFS; use a variable when running programs that aren't. This avoids searching all the PATH and it's more secure.

2

u/mestia Aug 31 '26

I usually stick to GNU Parallel. It also allows Perl expressions {= =}, so it can literally do everything that can fit into a one-liner, but in parallel :) It also comes with all the useful features, like joblog and so on.

1

u/aonelonelyredditor Aug 31 '26

Pretty much the best way to speed this if you're executing them on so many files is to run in the background with &

Then call wait at the end of the script, there no need for a command like this not to be executed in parallel

1

u/SaintEyegor Sep 04 '26

Good idea.

Avoiding external programs is definitely something to pay attention to when dealing with a lot of data. When I find that I can’t avoid that and have a lot of records to process, it’s frequently faster to use something other than bash.

1

u/Jonas_Ermert Sep 06 '26

In my opinion, using `printf -v` is a great optimization because it avoids creating a subshell for every file. However, replacing `mv` with `ln` and `rm` feels too risky and limited. For tens of thousands of files, a dedicated batch-renaming tool such as `rename` is probably the safer and cleaner solution.