在bash输出中删除字符

时间:2016-11-23 00:45:46

标签: bash macos unix cross-platform sh

我有这个命令对我有用,它会在当前目录中找到与模式匹配的零个或一个目录:

find . -maxdepth 1 -type d -name 'suman-*'| head -n1
在MacOS上

,这将导致类似:

./suman-1479860474833

<!EDIT>

我的目标是找到最新的目录(具有最新的时间戳)。目录内容如下所示:

foo
bar 
baz
suman-1479860475524
suman-1479860471431
suman-1479860474233
...
etc.

</EDIT>

我有三个问题,

  1. 使用bash,如果结果中存在./字符,如何删除它们?他们应该永远在那里,但我想总是删除 前两个字符可能太卡利了。

  2. 使用bash,而不是使用head查找第一个结果,找到最后结果的最佳方法是什么?我猜它是tail,但也许在那里 是一种更好的方式。

  3. 有没有办法只匹配一个数字而不只是使用'suman-*'?也许我应该使用-regex代替-name

  4. 现在,我可以按目录名称中的时间戳对目录进行排序,或者我可以按其元数据对它们进行排序(如果元数据准确并且通过版本控制更新等持续存在)。我个人并不确定目录元数据是否足够持久,所以我想更加透明并使用目录名中的时间戳。 看起来在Unix中没有为目录创建时间=&gt; &#34;在Unix中,创建时间不存储(仅限:访问,修改和更改)。&#34;

4 个答案:

答案 0 :(得分:2)

使用GNU find,如果您不想./,则可以简单地避免告诉find打印它。

# with GNU find
find . -maxdepth 1 -type d -name 'suman-*' -printf '%P\n' | head -n1

%P格式字符串排除了从find的参数派生的文件名部分 - 在本例中为./

使用BSD find,你没有这个选项,但是一旦你的结果出现在shell变量中就可以进行后处理:

# strip "./" prefix from filename variable, if and only if it exists
filename=${filename#./}

至于headtail,订购无法保证,因此您无法依赖此类选项来查找特定文件。如果您想要最新,最旧,第一,最后等,那么您需要做一些额外的工作才能以可靠的方式实现这一目标。例如:

IFS= read -r -d '' filename \
  < <(find . -maxdepth 1 -type d -name 'suman-*' -printf '%P\0' | sort -z)

...将在流排序时读取第一个项目,并且......

IFS= read -r -d '' filename \
  < <(find . -maxdepth 1 -type d -name 'suman-*' -printf '%P\0' | sort -rz)

...将按相反方向排序,从而读取最后的内容。

便携性说明

请注意,find -print0sort -z都没有指定POSIX,但这两者在GNU工具链和当前MacOS中均可用。相比之下,find -printf需要GNU查找;这可以通过macports findutils包安装在MacOS上(将其安装为gfind

答案 1 :(得分:2)

获取最近已修改的文件夹(OS X兼容):

stat -f "%HT %Sm %i" -t %Y%m%d%H%M%S * | grep "^Directory" | cut -f2- -d ' ' | sort -rn | head -1 | cut -f2- -d ' ' | while read inode ; do find . -inum "$inode" | basename "$(cat -)" ; done

结果:suman-1479860475524


获取最近命名的suman-时间戳(OS X兼容):

find ./ -mindepth 1 -maxdepth 1 -type d -name "suman-*" -print0 | sort -zn | while IFS= read -d '' file ; do basename "$file" ; done | tail -1

结果:

  • suman-1479860475524


最近的文件夹(任何名称):

  • stat -f "%HT %Sm %i" -t %Y%m%d%H%M%S *:列出以机器可读时间戳开头的文件夹(以秒为单位(因此cut可以安全使用),并显示类型,阳极和时间戳(见https://www.freebsd.org/cgi/man.cgi?stat(1)):

    • Directory 20161124051357 17658795
      Directory 20161124051358 17658796
      Directory 20161124051356 17658793
      Directory 20161124051359 17658798
      Directory 20161124051400 17658800
      Directory 20161124051401 17658802
      
  • | grep "^Directory" | cut -f2- -d ' ':选择文件夹,并修剪“目录”

  • | sort -rn:数字排序,最新到最早

  • | head -1:仅限最近的

  • | cut -f2- -d ' ':仅显示inode组件

  • | while read inode ; do find . -inum "$inode" -print0:根据inode查找文件(有些人可能会认为此步骤不是必需的,但如果包含特殊字符的奇怪命名文件夹,它会返回完整的文件夹名称)

此时(如果我们在这里添加了; done),我们会:

  • ./suman-1479860475524

最后

  • | basename "$(cat -)" ; done:只返回文件夹的名称

    • suman-1479860475524



最近的suman-文件夹时间戳:

  • find ./ -mindepth 1 -maxdepth 1 -type d -name "suman-*" -print0:获取当前文件夹中的文件夹名称

  • | sort -zn:根据目录名称中的时间戳,而不是实际的文件系统修改时间戳,对它们进行数字排序。

  • while IFS= read -d '' file ; do basename "$file" ; done:从文件中删除目录位置字符和斜杠,并将文件列表输出为行分隔

  • | tail -1:只列出最新的一个。

在此示例中,结果恰好相同:

  • suman-1479860475524

...while read inode ; ...变得有用的奇数文件夹名称示例


<强> 1

  • mkdir $'some \r\t strange \n folder  \n\n name \n  totally nuts'

<强> 2

  • stat -f "%HT %Sm %i" -t %Y%m%d%H%M%S * | grep "^Directory" | cut -f2- -d ' ' | sort -rn | head -1 | cut -f2- -d ' ' | while read inode ; do find . -inum "$inode" -print0 | basename "$(cat -)" ; done
    

3. (输出)

  • some     strange 
     folder  
    
     name 
      totally nuts
    

具体问题

  

使用bash,我怎样才能删除./字符,如果它们存在于   结果? (希望他们永远在那里,但我想永远   删除前两个字符可能太过分了。)

  • 在结果文件
  • 上使用basename
  • 使用find "$PWD"代替find .(这将生成完整路径)
  • 使用-printf "%P"(这只显示没有./的名称部分)(注意: -printf需要GNU查找
  

使用bash,而不是找到头部的第一个结果是什么   找到最后结果的最好方法(我猜它是尾巴,但也许   有更好的方法)

一种方法是使用:

  • 第一个结果:find ... | sort -n | tail -n1
  • 最后结果:find ... | sort -rn | tail -n1

(显然“First”可能实际上是“Last”,具体取决于它对你的意义。你可以基本上用head命令代替这个事实,只要你保持一致,-rsort中将反转顺序,因此两个管道命令集将为您提供“第一个”和“最后”结果)

  

有没有办法只匹配一个数字(这是一个时间戳)   millis)而不是只使用'suman- *'?也许我应该使用-regex   而不是-name?

您可以-name '*1479860474833'代替-name 'suman-*'

GNU查找的替代(更简单)方法

此示例的三个测试文件夹:

  • 苏曼氏-14的 7 9860474833
  • 苏曼氏-14的 8 9860474833
  • 苏曼氏-14的 9 9860474833

示例1:

这是一个严格的例子,可以减少使用嵌入式特殊字符的疯狂文件夹名称的风险

  • find "$PWD" -mindepth 1 -maxdepth 1 -type d -print0 | sort -zn | tail -zn1
    

给出:

  • /my/dir/suman-1499860474833
    

示例2:

使用printf\0删除前导“./”,同时保持NULL分隔:

  • find "$PWD" -mindepth 1 -maxdepth 1 -type d -printf "%P \0" | sort -zn | tail -zn1
    

给出:

  • suman-1499860474833
    

注意-mindepth 1可以避免返回父文件夹。

答案 2 :(得分:1)

<强> TL;博士

printf '%s\n' suman-*/ | tail -n 1 | sed 's|/$||' # ... | cut -d/ -f1 works too

请注意,此答案假设文件名没有嵌入的换行符,幸运的是,这很少是现实世界的关注。
除非另有说明,否则本答案中的所有命令均符合POSIX标准。

如果您只在目标目录中查找直接子目录,那么就不需要find - 一个简单的 glob 会这样做:

printf '%s\n' suman-*/ | head -n 1

但请注意:

  • 这会输出带尾随/
  • 的子目录名称
  • 符号链接到目录 包含在内。
  • 隐藏的子目录包含在内(不关注suman-*/,根据定义,它永远不会与隐藏的目录匹配。) - 包含一般的隐藏项目,首先运行shopt -s dotglob(这是 Bash 扩展名)。
  • 输出排序是case- 敏感,即使macOS默认文件系统是case- 不敏感 - 要更改它,将printf输出传递给{{在进一步处理之前1}}或sort -f

关于查找与模式匹配的最近 modified 目录的请求,将sort -rf与glob组合是最简单的选项:

ls -dt

相反,如果目录名中嵌入的时间戳应该驱动排序(例如ls -dt suman-*/ | head -n 1 # print most recently modified suman-* subdir. ),反向词法排序将会:< / p>

1479860475524

没有尾随ls -dr suman-*/ | head -n 1

/

稍微繁琐,但更强大的替代,避免使用ls -dr suman-*/ | head -n 1 | sed 's|/$||' # with no path prefix, | cut -d/ -f1 works too 来支持避免最大值。调用外部实用程序时的命令行长度,如ls所报告的那样,如果大量文件与glob匹配则可能会引起关注:帽子提示{{1} 3}}

getconf ARG_MAX

注意:这假定printf '%s\n' suman-*/ | tail -n 1 | sed 's|/$||' 是作为 shell builtin 实现的(而不是必须依赖printf 实用程序),但是,对于所有类似POSIX的主要shell(printfbashzshksh)都是如此。

使用dash的不区分大小写的替代方案(在此方案中没有任何区别):

sort

关于你的3个原始问题:

重新1):使用printf '%s\n' suman-*/ | sort -rf | head -n 1 | sed 's|/$||' 修剪尾随sed/(如果剥离路径前缀也是一个问题,它最容易printf '%s\n' suman-*/ | head -n 1 | sed 's|/$||'首先是路径前缀,然后使用仅文件名的glob。。

Re 2):使用cd获取词汇最后一个条目(或printf ... | tail -1 | ...以获得词汇最后一个条目,无论情况如何)。

Re 3):( globbing)模式允许每个字符位置的数字与printf ... | sort -rf | head -n 1 | ...等字符集匹配,但是你不能应用正则表达式的量词(重复符号),例如[0-9]?给他们。

一般来说,使用+和globbing / find之间存在许多微妙的差异 - 警告。 通常,Charles Duffy,但是 - 假设有人知道边缘情况和限制 - 有时这是最方便的解决方案。

答案 3 :(得分:0)

认为&#34;更简单更好&#34;:

# 1: Using cut (The option "-c 3-" means "from 3rd character"
find... | cut -c 3-

# 2: You're right, tail is the command (-n1 == -1)
find... | tail -1

# 3: -name should do it:
TIME=1479860474833
find... -name "suman-$TIME"

编辑:

正如你所说:

  

目录内容如下:    foo bar baz suman-1479860475524 suman-1479860471431 suman-1479860474233 ... etc.

最近

要根据名称获取最新目录,您可以执行以下操作:

$ find . -maxdepth 1 -type d -name suman-\* | cut -c3- | sort -rnk1.7 | head -1
# Result is:
suman-1479860475524

如果内容只有名称如&#39; suman *&#39; (从来没有具有该模式的文件),那么我认为这更容易:

$ ls -1d suman-* | sort -rnk1.7 | head -1
# Result is:
suman-1479860475524

在这两种情况下,排序选项-r(反向)带来最新的第一个,-k1.7将从第7个字符(名称的毫秒部分)中按数字-n排序。

<强>最旧

为了获取最旧的目录,根据名称,不要使用-r选项:

$ find . -maxdepth 1 -type d -name suman-\* | cut -c3- | sort -nk1.7 | head -1
# Result is:
suman-1479860471431

$ ls -1d suman-* | sort -nk1.7 | head -1
# Result is:
suman-1479860471431