Cheatsheets:LinuxCLI/FileOps: Difference between revisions

From Wikibase
Jump to navigation Jump to search
 
(13 intermediate revisions by 2 users not shown)
Line 4: Line 4:


New to the command line? Every command here has a built-in manual — try <syntaxhighlight lang="bash" inline>man ls</syntaxhighlight> or [https://tldr.sh tldr] for short examples; [https://explainshell.com explainshell.com] annotates any command line.
New to the command line? Every command here has a built-in manual — try <syntaxhighlight lang="bash" inline>man ls</syntaxhighlight> or [https://tldr.sh tldr] for short examples; [https://explainshell.com explainshell.com] annotates any command line.
== Basic example: a small file workflow ==
<syntaxhighlight lang="bash" line highlight="2,3,5,6" copy>
pwd                        # where am I?
mkdir -p notes/drafts      # create nested directories
cd notes/drafts
touch todo.txt            # create an empty file
cp todo.txt todo.bak      # copy
mv todo.bak ../todo.md    # move + rename in one step
ls -la                    # inspect the results
head -n 3 todo.txt        # peek at the contents
</syntaxhighlight>


== Navigation & listing ==
== Navigation & listing ==
Line 29: Line 16:


'''ls''' — the workhorse:
'''ls''' — the workhorse:
<syntaxhighlight lang="bash" copy>
ls [options] [dir]  # list directory contents
ls *.txt            # glob: only matching names
</syntaxhighlight>


{| class="wikitable"
{| class="wikitable"
! Command !! Shows
! Option !! Effect
|-
|-
| <syntaxhighlight lang="bash" inline>ls</syntaxhighlight> || names only
| <syntaxhighlight lang="bash" inline>-l</syntaxhighlight> || long format: permissions, owner, size, date
|-
|-
| <syntaxhighlight lang="bash" inline>ls -l</syntaxhighlight> || long format: permissions, owner, size, date
| <syntaxhighlight lang="bash" inline>-a</syntaxhighlight> || hidden files (dotfiles)
|-
|-
| <syntaxhighlight lang="bash" inline>ls -a</syntaxhighlight> || hidden files (dotfiles)
| <syntaxhighlight lang="bash" inline>-h</syntaxhighlight> || human-readable sizes (combine with -l)
|-
|-
| <syntaxhighlight lang="bash" inline>ls -h</syntaxhighlight> || human-readable sizes (combine with -l)
| <syntaxhighlight lang="bash" inline>-R</syntaxhighlight> || recurse into subdirectories
|-
|-
| <syntaxhighlight lang="bash" inline>ls -R</syntaxhighlight> || recurse into subdirectories
| <syntaxhighlight lang="bash" inline>-t</syntaxhighlight> || sort by modification time, newest first
|-
| <syntaxhighlight lang="bash" inline>ls -t</syntaxhighlight> || sort by modification time, newest first
|-
| <syntaxhighlight lang="bash" inline>ls *.txt</syntaxhighlight> || glob: only matching names
|}
|}


Line 51: Line 39:


<syntaxhighlight lang="bash" copy>
<syntaxhighlight lang="bash" copy>
mkdir newdir               # create a directory
mkdir newdir                     # create a directory
mkdir -p dir/subdir/newsubsubdir             # create parents as needed
mkdir -p dir/subdir/newsubsubdir # create parents as needed
mkdir -m 755 dir           # set permissions at creation
mkdir -m 755 dir                 # set permissions at creation
rmdir emptydir             # remove an EMPTY directory
rmdir emptydir                   # remove an EMPTY directory
rmdir -p dir/subdir/emptysubdir             # ... and its empty parents
rmdir -p dir/subdir/emptysubdir   # ... and its empty parents
touch file.txt             # create an empty file (or refresh its mtime)
touch file.txt                   # create an empty file (or refresh its mtime)
touch -t 202501011200 f   # set a specific timestamp
touch -t 202501011200 f           # set a specific timestamp
rm file.txt               # delete a file
rm file.txt                       # delete a file
rm -r dir/                 # delete a directory and its contents
rm -r dir/                       # delete a directory and its contents
rm -f file                 # never prompt, ignore missing files
rm -f file                       # never prompt, ignore missing files
rm -i file                 # prompt before each delete
rm -i file                       # prompt before each delete
</syntaxhighlight>
</syntaxhighlight>


Line 97: Line 85:


'''grep''' — search inside files:
'''grep''' — search inside files:
<syntaxhighlight lang="bash" copy>
grep [options] pattern file  # print matching lines
</syntaxhighlight>


{| class="wikitable"
{| class="wikitable"
! Command !! Does
! Option !! Effect
|-
|-
| <syntaxhighlight lang="bash" inline>grep pattern file</syntaxhighlight> || print matching lines
| <syntaxhighlight lang="bash" inline>-i</syntaxhighlight> || case-insensitive
|-
|-
| <syntaxhighlight lang="bash" inline>grep -i pattern file</syntaxhighlight> || case-insensitive
| <syntaxhighlight lang="bash" inline>-n</syntaxhighlight> || include line numbers
|-
|-
| <syntaxhighlight lang="bash" inline>grep -n pattern file</syntaxhighlight> || include line numbers
| <syntaxhighlight lang="bash" inline>-v</syntaxhighlight> || invert: lines that do NOT match
|-
|-
| <syntaxhighlight lang="bash" inline>grep -v pattern file</syntaxhighlight> || invert: lines that do NOT match
| <syntaxhighlight lang="bash" inline>-r</syntaxhighlight> || search a directory recursively
|-
|-
| <syntaxhighlight lang="bash" inline>grep -r pattern dir/</syntaxhighlight> || search a directory recursively
| <syntaxhighlight lang="bash" inline>-l</syntaxhighlight> || only filenames with matches
|-
|-
| <syntaxhighlight lang="bash" inline>grep -l pattern dir/*</syntaxhighlight> || only filenames with matches
| <syntaxhighlight lang="bash" inline>-c</syntaxhighlight> || count matching lines
|-
|-
| <syntaxhighlight lang="bash" inline>grep -c pattern file</syntaxhighlight> || count matching lines
| <syntaxhighlight lang="bash" inline>-E</syntaxhighlight> || extended regex, e.g. <syntaxhighlight lang="bash" inline>grep -E 'a|b' file</syntaxhighlight>
|-
| <syntaxhighlight lang="bash" inline>grep -E 'a|b' file</syntaxhighlight> || extended regex
|}
|}


Line 121: Line 111:


<syntaxhighlight lang="bash" copy>
<syntaxhighlight lang="bash" copy>
find . -name "*.py"           # by name (glob)
find . -name "*.py"                 # by name (glob)
find /home -type f           # only files
find /home -type f                   # only files
find /home -type d           # only directories
find /home -type d                   # only directories
find . -mtime -7             # modified in the last 7 days
find . -mtime -7                     # modified in the last 7 days
find . -size +100M           # larger than 100 MB
find . -size +100M                   # larger than 100 MB
find . -name "*.log" -delete # delete matches
find . -name "*.log" -delete         # delete matches
find . -name "*.tmp" -exec rm {} \;
find . -name "*.tmp" -exec <some-commands> {} \; # run a command on each match
</syntaxhighlight>
</syntaxhighlight>


Line 144: Line 134:
== Permissions & ownership ==
== Permissions & ownership ==


'''chmod''' — two notations. Numeric: one digit per owner/group/others, where r=4, w=2, x=1. Symbolic: <syntaxhighlight lang="bash" inline>u</syntaxhighlight>=owner, <syntaxhighlight lang="bash" inline>g</syntaxhighlight>=group, <syntaxhighlight lang="bash" inline>o</syntaxhighlight>=others, <syntaxhighlight lang="bash" inline>a</syntaxhighlight>=all, with <syntaxhighlight lang="bash" inline>+</syntaxhighlight> to add, <syntaxhighlight lang="bash" inline>-</syntaxhighlight> to remove, <syntaxhighlight lang="bash" inline>=</syntaxhighlight> to set.
'''chmod''' — change file/folder permissions.  


<syntaxhighlight lang="bash" copy>
<syntaxhighlight lang="bash" copy>
Line 158: Line 148:
file archive.tar        # detect the file type
file archive.tar        # detect the file type
</syntaxhighlight>
</syntaxhighlight>
<blockquote>
On GNU Linux, there are two notations for file permissions.
* Numeric: three digits representing permissions for owner|owner's group|others
** Each digit is an arithmetic sum of all accorded permissions: <code>4</code>:read, <code>2</code>:write, <code>1</code>:execute
* Symbolic: mostly used to add/remove a single permission.
** <code>r</code>:read, <code>w</code>:write, <code>x</code>:execute
** <code>u</code>=owner, <code>g</code>=owner's group, <code>o</code>=others, <code>a</code>=all
** <code>+</code>add, <code>-</code> to remove, <code>=</code> to set permission to exactly the given value.
Below are some common file permissions:


{| class="wikitable"
{| class="wikitable"
! Digit !! Permissions
! Numeric !! Symbolic
|-
|-
| 7 || rwx
| 7 || rwx
Line 170: Line 171:
| 4 || r--
| 4 || r--
|}
|}
</blockquote>


<blockquote>
<blockquote>
'''Reading <syntaxhighlight lang="bash" inline">ls -l</syntaxhighlight>:''' the first column is 10 characters — one type (<syntaxhighlight lang="text" inline>d</syntaxhighlight> directory, <syntaxhighlight lang="text" inline>-</syntaxhighlight> file, <syntaxhighlight lang="text" inline>l</syntaxhighlight> symlink) plus three triplets: owner, group, others. So <syntaxhighlight lang="text" inline>drwxr-xr-x</syntaxhighlight> is a directory, owner rwx, group r-x, others r-x.
You can read permissions of a given directory and its content with <code>ls -l</code>.
 
The command output will be something like this:
<pre>
drwxr-xr-x  2 rongzhou rongzhou    4096 Aŭg 20 16:19 9781474271325.sdr
-rw-rw-r--  1 rongzhou rongzhou    1758 Mar 10 15:26 antaurigardo.html
drwxrwxr-x  4 rongzhou rongzhou    4096 Mar 13 14:28 Arduino
drwxr-xr-x  4 rongzhou rongzhou    4096 Jul 22 12:20 Bildujo
</pre>
The first column is permission.
* There are always 10 chars.
* The first char is always <code>d</code> for directories and <code>-</code> for files.
* char 2-4 are permissions for owner in symbolic notation
* char 5-7 are permissions for owner's group
* char 8-10 are permissions for others
</blockquote>
</blockquote>


Line 182: Line 198:
du -h --max-depth=1 dir/ # per subdirectory
du -h --max-depth=1 dir/ # per subdirectory
df -h                    # free space per mounted filesystem
df -h                    # free space per mounted filesystem
df -h .                  # the filesystem holding the current dir
df -h .                  # ... only for the filesystem holding the current dir
</syntaxhighlight>
</syntaxhighlight>


Line 194: Line 210:


<blockquote>
<blockquote>
'''Prefer relative symlinks''' (<syntaxhighlight lang="bash" inline>ln -s ../target link</syntaxhighlight>) so they survive the tree being moved. Hard links cannot span filesystems or point at directories. <syntaxhighlight lang="bash" inline>ls -l</syntaxhighlight> shows a symlink's target with <syntaxhighlight lang="text" inline>-></syntaxhighlight>.
Hard links cannot span filesystems or point at directories. <syntaxhighlight lang="bash" inline>ls -l</syntaxhighlight> shows a symlink's target with <syntaxhighlight lang="text" inline>-></syntaxhighlight>.
</blockquote>
</blockquote>


== Archives ==
== Archives ==


'''tar''' — <syntaxhighlight lang="bash" inline>c</syntaxhighlight>=create, <syntaxhighlight lang="bash" inline>x</syntaxhighlight>=extract, <syntaxhighlight lang="bash" inline">t</syntaxhighlight>=list, <syntaxhighlight lang="bash" inline">z</syntaxhighlight>=gzip, <syntaxhighlight lang="bash" inline">j</syntaxhighlight>=bzip2; <syntaxhighlight lang="bash" inline">f</syntaxhighlight> always names the archive:
'''tar'''


<syntaxhighlight lang="bash" copy>
<syntaxhighlight lang="bash" copy>
Line 208: Line 224:
tar -cjf archive.tar.bz2 dir/      # bzip2 instead of gzip
tar -cjf archive.tar.bz2 dir/      # bzip2 instead of gzip
</syntaxhighlight>
</syntaxhighlight>
{| class="wikitable"
! Option !! Effect
|-
| <syntaxhighlight lang="bash" inline>-c</syntaxhighlight> || create an archive
|-
| <syntaxhighlight lang="bash" inline>-x</syntaxhighlight> || extract
|-
| <syntaxhighlight lang="bash" inline>-t</syntaxhighlight> || list contents
|-
| <syntaxhighlight lang="bash" inline>-f</syntaxhighlight> || the archive file name — always followed by the archive
|-
| <syntaxhighlight lang="bash" inline>-z</syntaxhighlight> || gzip compression → <syntaxhighlight lang="text" inline>.tar.gz</syntaxhighlight>
|-
| <syntaxhighlight lang="bash" inline>-j</syntaxhighlight> || bzip2 compression → <syntaxhighlight lang="text" inline>.tar.bz2</syntaxhighlight>
|-
| <syntaxhighlight lang="bash" inline>-v</syntaxhighlight> || verbose: list each file as it is processed
|-
| <syntaxhighlight lang="bash" inline>-C</syntaxhighlight> || change to a directory before extracting
|}


'''zip / unzip:'''
'''zip / unzip:'''
Line 241: Line 277:
'''<syntaxhighlight lang="bash" inline>></syntaxhighlight> overwrites, <syntaxhighlight lang="bash" inline>>></syntaxhighlight> appends''' — one character decides whether an existing file survives.
'''<syntaxhighlight lang="bash" inline>></syntaxhighlight> overwrites, <syntaxhighlight lang="bash" inline>>></syntaxhighlight> appends''' — one character decides whether an existing file survives.
</blockquote>
</blockquote>
'''rg''' — ripgrep: a very fast recursive text search, the modern alternative to <syntaxhighlight lang="bash" inline>grep -r</syntaxhighlight> when you don't know the exact file (install: <syntaxhighlight lang="text" inline>apt install ripgrep</syntaxhighlight>, <syntaxhighlight lang="text" inline>brew install ripgrep</syntaxhighlight>):
<syntaxhighlight lang="bash" copy>
rg pattern [path]          # recursive search; path defaults to the current directory
rg pattern file.txt        # search a single file
rg -i 'error|warning' src/  # -i: case-insensitive
rg -n 'TODO' app/          # -n: include line numbers (on by default on a terminal)
rg -v 'DEBUG' app/          # -v: lines that do NOT match
rg -w 'foo' app/            # -w: whole-word matches only
rg -F 'a+b' app/            # -F: literal string, no regex metacharacters
rg -l 'TODO' app/          # -l: only filenames with matches
rg -c 'error' app/          # -c: count matching lines per file
rg -C 3 'panic' app/        # -C N: N lines of context around each match
rg -t py 'import' app/      # -t TYPE: only files of a type (py, js, rs, ...)
rg -g '*.log' 'error' .    # -g GLOB: only files matching the glob
rg -g '!*.min.js' 'var' .  # -g '!glob': exclude files matching the glob
rg --hidden 'secret' .      # --hidden: also search hidden dotfiles
rg --no-ignore 'tmp' .      # --no-ignore: ignore .gitignore / .ignore rules
rg -uu 'pattern' .          # -uu: also hidden files; -uuu also binary (each -u disables more filtering)
</syntaxhighlight>
<blockquote>
'''rg only prints — it never edits files.''' By default it skips hidden files, binary files and anything matched by <syntaxhighlight lang="text" inline>.gitignore</syntaxhighlight>; add <syntaxhighlight lang="bash" inline>-uu</syntaxhighlight> when results look incomplete. <syntaxhighlight lang="bash" inline>-r</syntaxhighlight> / <syntaxhighlight lang="bash" inline>--replace</syntaxhighlight> rewrites the matched text in the output only — use it to preview what a replacement would look like, then apply the real edit with sd below.
</syntaxhighlight>
</blockquote>
'''sd''' — search & displace: an intuitive regex find-and-replace, a friendlier <syntaxhighlight lang="bash" inline>sed</syntaxhighlight> (install: <syntaxhighlight lang="text" inline>cargo install sd</syntaxhighlight>, <syntaxhighlight lang="text" inline>brew install sd</syntaxhighlight>):
<syntaxhighlight lang="bash" copy>
sd old_text new_text file.txt            # replace every occurrence of old_text, in place
sd old_text new_text file1.txt file2.txt # edit several files at once
sd old_text new_text **/*.md            # glob for all .md files in the directory and its subdirectories [1]
sd 'foo\s+bar' 'baz' file.txt            # regex find & replace (JS/Python-style syntax)
sd '(\w+) (\w+)' '$2 $1' file.txt        # regex capture groups: the example swap two words
sd -F 'a+b' 'axb' file.txt              # -F: literal text, '+' is not regex "one or more"
cat file.txt | sd -A '\n' ','            # -A: pattern may match across line breaks (join lines)
sd -p old_text new_text file.txt        # -p: preview the result, do not write
echo 'some_text' | sd old_text new_text  # no file given: read stdin, write stdout
sd old_text -- '-new_text' file.txt      # --: declare end of flags so new_text may start with a dash
</syntaxhighlight>
<blockquote>
'''sd replaces every occurrence and edits files in place, which is non-reversible.'''Dry-run with <syntaxhighlight lang="bash" inline>-p</syntaxhighlight> and keep files under version control.
</blockquote>
<blockquote>
[1]: You must enable globstar for <code>**</code> to be interpreted correctly.
Add to <code><nowiki>~/.bashrc</nowiki></code>:
<syntaxhighlight lang="bash" copy>
shopt -s globstar
</syntaxhighlight>
</blockquote>
== File format conversion ==
See [[Cheatsheets:Pandoc]]


== Further reading ==
== Further reading ==

Latest revision as of 12:14, 2 September 2026

Languages: English · français · Esperanto

Quick reference for smart people — part of our Linux cheatsheets collection.

The everyday Linux command-line file operations: navigate, create, copy, move, inspect, search, and archive files.

New to the command line? Every command here has a built-in manual — try man ls or tldr for short examples; explainshell.com annotates any command line.

pwd              # print the current directory
cd dir           # enter dir
cd ..            # go up one level
cd ~             # home directory
cd -             # previous directory

ls — the workhorse:

ls [options] [dir]   # list directory contents
ls *.txt             # glob: only matching names
Option Effect
-l long format: permissions, owner, size, date
-a hidden files (dotfiles)
-h human-readable sizes (combine with -l)
-R recurse into subdirectories
-t sort by modification time, newest first

Creating & removing

mkdir newdir                      # create a directory
mkdir -p dir/subdir/newsubsubdir  # create parents as needed
mkdir -m 755 dir                  # set permissions at creation
rmdir emptydir                    # remove an EMPTY directory
rmdir -p dir/subdir/emptysubdir   # ... and its empty parents
touch file.txt                    # create an empty file (or refresh its mtime)
touch -t 202501011200 f           # set a specific timestamp
rm file.txt                       # delete a file
rm -r dir/                        # delete a directory and its contents
rm -f file                        # never prompt, ignore missing files
rm -i file                        # prompt before each delete

rm -rf is irreversible — there is no trash bin. Double-check the path (and your current directory with pwd) before deleting recursively.

Copying & moving

cp file.txt backup.txt      # copy a file
cp -r src/ dest/            # copy a directory recursively
cp -a src/ dest/            # recursive + preserve attributes
cp -i file.txt dest/        # prompt before overwriting
cp file1 file2 dest/        # several files into a directory
mv file.txt newname.txt     # rename
mv file.txt dir/            # move into a directory
mv -i file.txt dir/         # prompt before overwriting
mv src/ dest/               # move/rename a directory

Viewing files

cat file.txt            # print the whole file
cat file1 file2         # concatenate, in order
cat -n file.txt         # with line numbers
less file.txt           # scrollable pager: /search, g/G jump,f/b move forward/backward one page, q quit
head -n 5 file.txt      # show first 5 lines of a file. If no -n parsed, default is 10
tail -n 50 app.log      # show last 50 lines of a file. If no -n parsed, default is 10
tail -f app.log         # follow a growing log, Ctrl+C to stop

Searching

grep — search inside files:

grep [options] pattern file   # print matching lines
Option Effect
-i case-insensitive
-n include line numbers
-v invert: lines that do NOT match
-r search a directory recursively
-l only filenames with matches
-c count matching lines
-E extended regex, e.g. grep -E 'a|b' file

find — search by name, type, age, size:

find . -name "*.py"                  # by name (glob)
find /home -type f                   # only files
find /home -type d                   # only directories
find . -mtime -7                     # modified in the last 7 days
find . -size +100M                   # larger than 100 MB
find . -name "*.log" -delete         # delete matches
find . -name "*.tmp" -exec <some-commands> {} \;  # run a command on each match

locate — faster index-based search:

locate file.txt    # instant filename lookup
locate folder/subfolder    # instant partial path lookup
sudo updatedb           # refresh the index (requires root permission)

locate searches an index refreshed by updatedb — files created since the last update are invisible to it. find is slower but always up to date.

Permissions & ownership

chmod — change file/folder permissions.

chmod 755 script.sh     # rwxr-xr-x
chmod 644 file.txt      # rw-r--r--
chmod u+x script.sh     # add execute for the owner
chmod -R 755 dir/       # recursive
chown alice file.txt        # change owner
chown alice:dev file.txt    # owner and group
chown -R alice:dev dir/
chgrp dev file.txt       # change group only
stat file.txt            # perms, size, timestamps, inode
file archive.tar         # detect the file type

On GNU Linux, there are two notations for file permissions.

  • Numeric: three digits representing permissions for owner|owner's group|others
    • Each digit is an arithmetic sum of all accorded permissions: 4:read, 2:write, 1:execute
  • Symbolic: mostly used to add/remove a single permission.
    • r:read, w:write, x:execute
    • u=owner, g=owner's group, o=others, a=all
    • +add, - to remove, = to set permission to exactly the given value.

Below are some common file permissions:

Numeric Symbolic
7 rwx
6 rw-
5 r-x
4 r--

You can read permissions of a given directory and its content with ls -l.

The command output will be something like this:

drwxr-xr-x  2 rongzhou rongzhou    4096 Aŭg 20 16:19 9781474271325.sdr
-rw-rw-r--  1 rongzhou rongzhou    1758 Mar 10 15:26 antaurigardo.html
drwxrwxr-x  4 rongzhou rongzhou    4096 Mar 13 14:28 Arduino
drwxr-xr-x  4 rongzhou rongzhou    4096 Jul 22 12:20 Bildujo

The first column is permission.

  • There are always 10 chars.
  • The first char is always d for directories and - for files.
  • char 2-4 are permissions for owner in symbolic notation
  • char 5-7 are permissions for owner's group
  • char 8-10 are permissions for others

Disk usage

du -sh dir/              # total size of a directory, human-readable
du -sh *                 # size of each item
du -h --max-depth=1 dir/ # per subdirectory
df -h                    # free space per mounted filesystem
df -h .                  # ... only for the filesystem holding the current dir
ln target hardlink          # hard link: same file, another name
ln -s target symlink        # symbolic link: points to a path
ln -s /usr/local/bin/tool ~/bin/tool

Hard links cannot span filesystems or point at directories. ls -l shows a symlink's target with ->.

Archives

tar

tar -czf archive.tar.gz dir/       # create a gzipped archive
tar -xzf archive.tar.gz            # extract (compression auto-detected)
tar -tzf archive.tar.gz            # list contents
tar -xzf archive.tar.gz -C dest/   # extract into a directory
tar -cjf archive.tar.bz2 dir/      # bzip2 instead of gzip
Option Effect
-c create an archive
-x extract
-t list contents
-f the archive file name — always followed by the archive
-z gzip compression → .tar.gz
-j bzip2 compression → .tar.bz2
-v verbose: list each file as it is processed
-C change to a directory before extracting

zip / unzip:

zip -r archive.zip dir/     # zip a directory recursively
zip archive.zip a.txt b.txt # zip specific files
unzip archive.zip           # extract
unzip -l archive.zip        # list contents
unzip archive.zip -d dest/  # extract into a directory
unzip -o archive.zip        # overwrite without asking

Text utilities

sort file.txt            # sort lines alphabetically
sort -n file.txt         # numeric sort
sort -r file.txt         # reverse
sort -u file.txt         # unique lines
sort -k2 file.txt        # sort by the 2nd field
diff file1 file2         # line-by-line differences
diff -u file1 file2      # unified diff (patch format)
diff -r dir1 dir2        # compare directories
cmp file1 file2          # byte compare: report first difference
echo "hello"             # print text
echo -n "no newline"     # omit the trailing newline
echo "hi" > file.txt     # overwrite a file with output
echo "hi" >> file.txt    # append to a file

> overwrites, >> appends — one character decides whether an existing file survives.

rg — ripgrep: a very fast recursive text search, the modern alternative to grep -r when you don't know the exact file (install: apt install ripgrep, brew install ripgrep):

rg pattern [path]           # recursive search; path defaults to the current directory
rg pattern file.txt         # search a single file
rg -i 'error|warning' src/  # -i: case-insensitive
rg -n 'TODO' app/           # -n: include line numbers (on by default on a terminal)
rg -v 'DEBUG' app/          # -v: lines that do NOT match
rg -w 'foo' app/            # -w: whole-word matches only
rg -F 'a+b' app/            # -F: literal string, no regex metacharacters
rg -l 'TODO' app/           # -l: only filenames with matches
rg -c 'error' app/          # -c: count matching lines per file
rg -C 3 'panic' app/        # -C N: N lines of context around each match
rg -t py 'import' app/      # -t TYPE: only files of a type (py, js, rs, ...)
rg -g '*.log' 'error' .     # -g GLOB: only files matching the glob
rg -g '!*.min.js' 'var' .   # -g '!glob': exclude files matching the glob
rg --hidden 'secret' .      # --hidden: also search hidden dotfiles
rg --no-ignore 'tmp' .      # --no-ignore: ignore .gitignore / .ignore rules
rg -uu 'pattern' .          # -uu: also hidden files; -uuu also binary (each -u disables more filtering)

rg only prints — it never edits files. By default it skips hidden files, binary files and anything matched by .gitignore; add -uu when results look incomplete. -r / --replace rewrites the matched text in the output only — use it to preview what a replacement would look like, then apply the real edit with sd below. </syntaxhighlight>

sd — search & displace: an intuitive regex find-and-replace, a friendlier sed (install: cargo install sd, brew install sd):

sd old_text new_text file.txt            # replace every occurrence of old_text, in place
sd old_text new_text file1.txt file2.txt # edit several files at once
sd old_text new_text **/*.md             # glob for all .md files in the directory and its subdirectories [1]
sd 'foo\s+bar' 'baz' file.txt            # regex find & replace (JS/Python-style syntax)
sd '(\w+) (\w+)' '$2 $1' file.txt        # regex capture groups: the example swap two words
sd -F 'a+b' 'axb' file.txt               # -F: literal text, '+' is not regex "one or more"
cat file.txt | sd -A '\n' ','            # -A: pattern may match across line breaks (join lines)
sd -p old_text new_text file.txt         # -p: preview the result, do not write
echo 'some_text' | sd old_text new_text   # no file given: read stdin, write stdout
sd old_text -- '-new_text' file.txt      # --: declare end of flags so new_text may start with a dash

sd replaces every occurrence and edits files in place, which is non-reversible.Dry-run with -p and keep files under version control.

[1]: You must enable globstar for ** to be interpreted correctly.

Add to ~/.bashrc:

shopt -s globstar

File format conversion

See Cheatsheets:Pandoc

Further reading