I have a directory containg several files, including those files some are bash scripts but their name doesn't include the .sh extension. I know I need to use the file command to know the type of files with -type option, I also know that I can redirect the output with xargs so I can rename the files with their origianl name + the .sh extension. The problem is I don't know how to pipe all the commands together, and how to find files type of only bash ASCII text executable. Any idea how to do that with a simple command?

  • How are you determining which files are bash scripts? Feb 15, 2021 at 2:00
  • @NasirRiley This is the main problem actually, idk how to determine only bash script files
    – William
    Feb 15, 2021 at 2:04
  • The file command is not overly reliable. For example, create a script that starts with twenty comment lines other than #!/bin/bash. file will categorize it as ASCII text (at least my version of file does). Feb 15, 2021 at 2:06
  • 1
    If you trust file, you can generate the list of shell scripts with file * | grep shell | cut -f1 -d: (assuming filenames don't contain colons). You step through this list with a for loop and rename the files. Feb 15, 2021 at 2:07
  • @berndbausch note that the #!/bin/bash line must always be the first line; anything not on the first line is ignored (and the kernel will run the script with /bin/sh) Feb 15, 2021 at 3:48

2 Answers 2


If you've set up any of the standard shebang lines it looks like file (at least version 5.39) should be able to detect them:

$ echo '#!/usr/bin/env bash' > 1
$ echo '#!/bin/bash' > 2
$ echo '#!/usr/bin/bash' > 3
$ file *
1: Bourne-Again shell script, ASCII text executable
2: Bourne-Again shell script, ASCII text executable
3: Bourne-Again shell script, ASCII text executable

So we should be able to do this:

for path in ./*
    if [[ "$(file "$path")" = "${path}: Bourne-Again shell script, ASCII text executable" ]]
        mv "$path" "${path}.bash"

This works for the above example files (and should work for unusual filenames, including those containing space characters, starting with a hyphen, or ending with a newline):

$ ls
1.bash  2.bash  3.bash

This will probably not work if your files do not have shebang lines (which is reasonable for files which are sourced by other scripts or the shebang lines are wrong (such as #!/bin/sh).

(I've used ".bash" since that is more explicit than ".sh".)

  • Be careful. I just wrote a tiny script with accented characters, and I get told /tmp/xxx: Bourne-Again shell script, UTF-8 Unicode text executable. The file is not executable, by the way (Fedora 33, UTF-8).
    – vonbrand
    Feb 16, 2021 at 0:51
  • @vonbrand file is fallible for sure - after all, it's a heuristic. I suspect "executable" here is probably meant in the "it's something you can run" sense rather than "it's a file with the executable flag set." Which is unfortunate but not technically wrong.
    – l0b0
    Feb 16, 2021 at 5:43
  • @alecxs You might want to post that as a separate answer with a bit of context.
    – l0b0
    Feb 16, 2021 at 20:19
  • what I'm concerned about is that the pattern you are looking for is not correct.
    – vonbrand
    Feb 28, 2021 at 20:30
  • @vonbrand The actual output of file is irrelevant here, the important thing is that it accurately identifies Bash scripts.
    – l0b0
    Feb 28, 2021 at 23:58

although that is not fulfilling your homework there is no need for find as grep kann search directories for files recursively with -r (and even that is not needed when file names are passed with globbing)

grep -lIw '^#!.*bash' dir/!(*.sh) | xargs -I_ mv _ _.sh

grep -r dir will search all files in all subdirectories (not used here)

grep -l is similar to -H print the file name for each match, but suppress normal output. furthermore it stops after first match similar to -q or -m1

grep -I exclude binary files, process text files only

grep -w bash will match whole word only (won't match bashed)

regex ^ #! match begin of line only (won't match ##!)

regex . match any byte * any occurrence = .* any string

pathname expansion (globbing) *.sh matches pattern file.sh

extended globbing !(pattern) matches anything except pattern

ls dir/!(*.sh) list all files in dir excluding dir/file.sh files

xargs -I pattern will insert arguments on each position pattern is used _ in this example:

mv   _     _  .sh
mv file1 file1.sh
mv file2 file2.sh

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.